Compare commits
48 Commits
4c3b3aeafc
...
0.4.2
| Author | SHA1 | Date | |
|---|---|---|---|
| bad5dff2b2 | |||
| e2af31b812 | |||
| 6c76dae760 | |||
| 28951df9b5 | |||
| 5aa290c772 | |||
| 13a70990e5 | |||
| a3fab92e3d | |||
| 4fdb7e9717 | |||
| 1d13f6e05d | |||
| fcb000afb0 | |||
| 0730b9f567 | |||
| 081aa8d17f | |||
| 29fe06462e | |||
| 8b20eb1220 | |||
| 5333690c47 | |||
| d82fc4ea46 | |||
| df2f1d0d81 | |||
| 9192ca3e4a | |||
| e37ca6eeac | |||
| 657a62eab5 | |||
| d8e2be013a | |||
| a91ec12d22 | |||
| 9c862863e5 | |||
| fc59e52337 | |||
| 3e93a41400 | |||
| c2cd18386b | |||
| 2bca3dd75a | |||
| 469bd9bc1f | |||
| 027d1e2d55 | |||
| 7cf0012229 | |||
| edc014d98c | |||
| 1813ec213b | |||
| 1b700086f2 | |||
| 02a6ffc931 | |||
| 2dfedfea57 | |||
| 8012d9b8ed | |||
| 3a6faa85be | |||
| 1af61aa3c7 | |||
| 315838604e | |||
| f368e6717c | |||
| 91e2c9d123 | |||
| 91d9c37612 | |||
| b5f2c272bb | |||
| eec72c77ab | |||
| 160ec2b48b | |||
| 7c57f939c0 | |||
| c233b020fc | |||
| 869a8ae79f |
@@ -2,4 +2,7 @@ DOCKER_REPO=git.shmyga.ru
|
|||||||
DOCKER_GROUP=infernalgames
|
DOCKER_GROUP=infernalgames
|
||||||
DOCKER_ROOT="$DOCKER_REPO/$DOCKER_GROUP"
|
DOCKER_ROOT="$DOCKER_REPO/$DOCKER_GROUP"
|
||||||
VERSION=$(grep -m 1 'version' ./pyproject.toml | grep -oP 'version\s*=\s*"\K[^"]+')
|
VERSION=$(grep -m 1 'version' ./pyproject.toml | grep -oP 'version\s*=\s*"\K[^"]+')
|
||||||
|
PYTHON_VERSION=$(grep -m 1 'python' ./pyproject.toml | grep -oP 'python\s*=\s*"\^?\K[^"]+')
|
||||||
DOCKER_PROJECTS=("gallery")
|
DOCKER_PROJECTS=("gallery")
|
||||||
|
|
||||||
|
OPENWEATHER_KEY="<EMPTY>"
|
||||||
1
.gitignore
vendored
@@ -5,3 +5,4 @@
|
|||||||
#.vscode
|
#.vscode
|
||||||
static/node_modules
|
static/node_modules
|
||||||
static/dist
|
static/dist
|
||||||
|
.env
|
||||||
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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
24
Dockerfile
@@ -1,33 +1,35 @@
|
|||||||
FROM python:3.12 AS builder
|
FROM python:3.14 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 --mount=type=cache,target=/root/.cache/pypoetry/cache \
|
||||||
|
--mount=type=cache,target=/root/.cache/pypoetry/artifacts \
|
||||||
|
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
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY static/package.json static/package-lock.json ./
|
COPY static/package.json static/package-lock.json ./
|
||||||
RUN npm ci
|
RUN --mount=type=cache,target=/root/.npm \
|
||||||
|
npm ci
|
||||||
COPY static ./
|
COPY static ./
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM python:3.12-slim
|
FROM python:3.14-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 --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
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ services:
|
|||||||
build: .
|
build: .
|
||||||
environment:
|
environment:
|
||||||
- REDIS_HOST=redis
|
- REDIS_HOST=redis
|
||||||
|
- OPENWEATHER_KEY=$OPENWEATHER_KEY
|
||||||
- DEBUG=1
|
- DEBUG=1
|
||||||
ports:
|
ports:
|
||||||
- 8000:80
|
- 8000:80
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ services:
|
|||||||
image: ${DOCKER_ROOT}/gallery
|
image: ${DOCKER_ROOT}/gallery
|
||||||
environment:
|
environment:
|
||||||
- REDIS_HOST=redis
|
- REDIS_HOST=redis
|
||||||
|
- OPENWEATHER_KEY=$OPENWEATHER_KEY
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
BIN
docs/screenshot.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
@@ -8,6 +8,7 @@
|
|||||||
"python.testing.pytestArgs": ["tests", "-s"],
|
"python.testing.pytestArgs": ["tests", "-s"],
|
||||||
"python.testing.unittestEnabled": false,
|
"python.testing.unittestEnabled": false,
|
||||||
"python.testing.pytestEnabled": true,
|
"python.testing.pytestEnabled": true,
|
||||||
|
"python.terminal.useEnvFile": true,
|
||||||
"python-envs.pythonProjects": [
|
"python-envs.pythonProjects": [
|
||||||
{
|
{
|
||||||
"path": ".",
|
"path": ".",
|
||||||
@@ -34,6 +35,8 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"module": "uvicorn",
|
"module": "uvicorn",
|
||||||
"args": ["gallery.main:app", "--reload", "--log-config", "gallery/logging.yaml"],
|
"args": ["gallery.main:app", "--reload", "--log-config", "gallery/logging.yaml"],
|
||||||
|
"justMyCode": true,
|
||||||
|
"consoleTitle": "gallery:app",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "gallery:static",
|
"name": "gallery:static",
|
||||||
@@ -41,6 +44,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "node-terminal",
|
"type": "node-terminal",
|
||||||
"command": "npm run dev",
|
"command": "npm run dev",
|
||||||
|
"consoleTitle": "gallery:static",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
import locale as _locale
|
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from gallery.sketch.bundle import ApiBundle
|
from gallery.sketch.bundle import ApiBundle
|
||||||
from gallery.util import root_path
|
from gallery.util import root_path
|
||||||
|
|
||||||
from .route import api, doc
|
from .route import api, doc, view
|
||||||
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:
|
def build_app(api_bundle: ApiBundle) -> FastAPI:
|
||||||
_locale.setlocale(_locale.LC_TIME, locale)
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="Gallery",
|
title="Gallery",
|
||||||
docs_url=None,
|
docs_url=None,
|
||||||
@@ -22,6 +16,6 @@ def build_app(api_bundle: ApiBundle, *, locale: str = DEFAULT_LOCALE) -> FastAPI
|
|||||||
app.state.api = api_bundle
|
app.state.api = api_bundle
|
||||||
app.mount("/static", StaticFiles(directory=root_path / "static/dist"))
|
app.mount("/static", StaticFiles(directory=root_path / "static/dist"))
|
||||||
doc.mount(app)
|
doc.mount(app)
|
||||||
api.mount(app)
|
app.include_router(api.router)
|
||||||
app.include_router(view_router)
|
app.include_router(view.router)
|
||||||
return app
|
return app
|
||||||
|
|||||||
0
gallery/easel/depends/__init__.py
Normal file
17
gallery/easel/depends/api.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from fastapi import HTTPException, status
|
||||||
|
|
||||||
|
from gallery.easel.core import AppRequest
|
||||||
|
from gallery.sketch.api import API
|
||||||
|
|
||||||
|
|
||||||
|
def api_resolver(api_type: type[API]):
|
||||||
|
def get_api(request: AppRequest, provider: str) -> API:
|
||||||
|
providers = request.app.state.api.get_api_providers(api_type)
|
||||||
|
if provider not in providers:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail={"provider": f"'{provider}' not in {providers}"},
|
||||||
|
)
|
||||||
|
return request.app.state.api.get_api(api_type, provider)
|
||||||
|
|
||||||
|
return get_api
|
||||||
9
gallery/easel/depends/schedule.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import Depends
|
||||||
|
|
||||||
|
from gallery.sketch.schedule.api import ScheduleApi
|
||||||
|
|
||||||
|
from .api import api_resolver
|
||||||
|
|
||||||
|
ScheduleApiDepends = Annotated[ScheduleApi, Depends(api_resolver(ScheduleApi))]
|
||||||
9
gallery/easel/depends/weather.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import Depends
|
||||||
|
|
||||||
|
from gallery.sketch.weather.api import WeatherApi
|
||||||
|
|
||||||
|
from .api import api_resolver
|
||||||
|
|
||||||
|
WeatherApiDepends = Annotated[WeatherApi, Depends(api_resolver(WeatherApi))]
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
from fastapi import FastAPI
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from . import schedule, weather
|
from . import schedule, weather
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api")
|
||||||
def mount(app: FastAPI):
|
router.include_router(weather.router)
|
||||||
weather.mount(app)
|
router.include_router(schedule.router)
|
||||||
schedule.mount(app)
|
|
||||||
|
|||||||
@@ -1,20 +1,27 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from gallery.easel.core import AppRequest
|
from gallery.easel.core import AppRequest
|
||||||
from gallery.sketch.schedule.model import ChannelId, Schedule
|
from gallery.easel.depends.schedule import ScheduleApiDepends
|
||||||
|
from gallery.sketch.schedule.api import ScheduleApi
|
||||||
|
from gallery.sketch.schedule.model import Channel, Schedule
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/schedule", tags=["Schedule"])
|
||||||
|
|
||||||
|
|
||||||
def mount(app: FastAPI):
|
@router.get("/providers")
|
||||||
@app.get("/api/schedule/channels", tags=["API"])
|
async def get_api_weather_providers(request: AppRequest) -> list[str]:
|
||||||
async def get_api_schedule_channels(request: AppRequest) -> list[ChannelId]:
|
return request.app.state.api.get_api_providers(ScheduleApi)
|
||||||
schedule_api = request.app.state.api.schedule
|
|
||||||
return await schedule_api.get_channels()
|
|
||||||
|
|
||||||
@app.get("/api/schedule/{channel}/{date}", tags=["API"])
|
|
||||||
async def get_api_schedule_channel_schedule(
|
@router.get("/{provider}/channels")
|
||||||
request: AppRequest, channel: str, date: datetime.date
|
async def find_api_schedule_channels(schedule_api: ScheduleApiDepends, query: str) -> list[Channel]:
|
||||||
) -> Schedule:
|
return await schedule_api.find_channels(query)
|
||||||
schedule_api = request.app.state.api.schedule
|
|
||||||
return await schedule_api.get_channel_schedule(ChannelId(channel), date)
|
|
||||||
|
@router.get("/{provider}/{channel}/{date}")
|
||||||
|
async def get_api_schedule_channel_schedule(
|
||||||
|
schedule_api: ScheduleApiDepends, channel: str, date: datetime.date
|
||||||
|
) -> Schedule:
|
||||||
|
return await schedule_api.get_schedule(channel, date)
|
||||||
|
|||||||
@@ -1,29 +1,30 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from gallery.easel.core import AppRequest
|
from gallery.easel.core import AppRequest
|
||||||
|
from gallery.easel.depends.weather import WeatherApiDepends
|
||||||
|
from gallery.sketch.weather.api import WeatherApi
|
||||||
from gallery.sketch.weather.model import Location, WeatherResponse
|
from gallery.sketch.weather.model import Location, WeatherResponse
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/weather", tags=["Weather"])
|
||||||
|
|
||||||
def mount(app: FastAPI):
|
|
||||||
@app.get("/api/weather/locations", tags=["API"])
|
|
||||||
async def get_api_weather_locations(
|
|
||||||
request: AppRequest, query: str
|
|
||||||
) -> list[Location]:
|
|
||||||
weather_api = request.app.state.api.weather
|
|
||||||
return await weather_api.find_locations(query)
|
|
||||||
|
|
||||||
@app.get("/api/weather/{location}/day/{date}", tags=["API"])
|
@router.get("/providers")
|
||||||
async def get_api_weather_day(
|
async def get_api_weather_providers(request: AppRequest) -> list[str]:
|
||||||
request: AppRequest, location: str, date: datetime.date
|
return request.app.state.api.get_api_providers(WeatherApi)
|
||||||
) -> WeatherResponse:
|
|
||||||
weather_api = request.app.state.api.weather
|
|
||||||
return await weather_api.get_day(location, date)
|
|
||||||
|
|
||||||
@app.get("/api/weather/{location}/days/{days}", tags=["API"])
|
|
||||||
async def get_api_weather_days(
|
@router.get("/{provider}/locations")
|
||||||
request: AppRequest, location: str, days: int
|
async def find_api_weather_locations(weather_api: WeatherApiDepends, query: str) -> list[Location]:
|
||||||
) -> WeatherResponse:
|
return await weather_api.find_locations(query)
|
||||||
weather_api = request.app.state.api.weather
|
|
||||||
return await weather_api.get_days(location, days)
|
|
||||||
|
@router.get("/{provider}/{location}/day/{date}")
|
||||||
|
async def get_api_weather_day(weather_api: WeatherApiDepends, location: str, date: datetime.date) -> WeatherResponse:
|
||||||
|
return await weather_api.get_day(location, date)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{provider}/{location}/days/{days}")
|
||||||
|
async def get_api_weather_days(weather_api: WeatherApiDepends, location: str, days: int) -> WeatherResponse:
|
||||||
|
return await weather_api.get_days(location, days)
|
||||||
|
|||||||
636
gallery/easel/route/doc/static/redoc.standalone.js
vendored
@@ -1,11 +1,11 @@
|
|||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
|
|
||||||
from .common import router as common_router
|
from .root import router as root_router
|
||||||
from .schedule import router as schedule_router
|
from .schedule import router as schedule_router
|
||||||
from .translation import set_language
|
from .translation import set_language
|
||||||
from .weather import router as weather_router
|
from .weather import router as weather_router
|
||||||
|
|
||||||
router = APIRouter(dependencies=[Depends(set_language)])
|
router = APIRouter(tags=["view"], dependencies=[Depends(set_language)], include_in_schema=False)
|
||||||
router.include_router(common_router)
|
router.include_router(root_router)
|
||||||
router.include_router(weather_router)
|
router.include_router(weather_router)
|
||||||
router.include_router(schedule_router)
|
router.include_router(schedule_router)
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
from typing import NamedTuple
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Request
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
|
|
||||||
from gallery.version import __version__
|
|
||||||
|
|
||||||
from ..translation import _
|
|
||||||
|
|
||||||
|
|
||||||
class Section(NamedTuple):
|
|
||||||
link: str
|
|
||||||
title: str
|
|
||||||
icon: str
|
|
||||||
|
|
||||||
|
|
||||||
SECTIONS = [
|
|
||||||
Section("weather", "Weather", "brightness-high"),
|
|
||||||
Section("schedule", "TV program", "tv"),
|
|
||||||
]
|
|
||||||
|
|
||||||
base_dir = Path(__file__).parent
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory=base_dir / "templates")
|
|
||||||
templates.env.globals.update({"_": _})
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_class=HTMLResponse)
|
|
||||||
async def get_section_list(request: Request):
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
request=request,
|
|
||||||
name="root_index.html",
|
|
||||||
context={
|
|
||||||
"version": __version__,
|
|
||||||
"sections": SECTIONS,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -19,12 +19,16 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body class="{{ is_widget and 'widget' or ''}}">
|
||||||
<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">
|
{% if not is_widget %}
|
||||||
<app-link href="/"
|
<header class="app-header pb-3 mb-5 border-bottom">
|
||||||
icon="gear">API Gallery</app-link>
|
<div>{{request.query_params.widget}}</div>
|
||||||
{% block header %}{% endblock %}
|
<div class="link-list">
|
||||||
|
<app-link href="/"
|
||||||
|
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 +36,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 icon-header"></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 +48,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 +57,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 +70,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 icon-header"></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 +81,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 +90,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,30 +99,27 @@
|
|||||||
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>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</header>
|
</header>
|
||||||
|
{% endif %}
|
||||||
<main>
|
<main>
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</main>
|
</main>
|
||||||
<footer class="pt-5 my-5 text-muted border-top">
|
{% if not is_widget %}
|
||||||
Created by shmyga · © 2026
|
<footer class="app-footer pt-5 my-5 text-muted border-top">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<span>Created by shmyga · © 2026</span>
|
||||||
|
<span>v{{ version }}</span>
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
const widget = params.get('widget') || window.location.hostname.startsWith('weather');
|
|
||||||
if (widget) {
|
|
||||||
document.body.classList.add('widget');
|
|
||||||
}
|
|
||||||
}());
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block title %}Index{% endblock %}
|
|
||||||
{% block head %}
|
|
||||||
{{ super() }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>View</h1>
|
|
||||||
<div class="list-group mb-5">
|
|
||||||
{% for section in sections %}
|
|
||||||
<a href="{{section.link}}"
|
|
||||||
class="list-group-item list-group-item-action px-4">
|
|
||||||
<app-link href="{{section.link}}"
|
|
||||||
icon="{{section.icon}}">
|
|
||||||
{{_(section.title)}}
|
|
||||||
</app-link>
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<hr class="col-3 col-md-2 mb-5">
|
|
||||||
<h1>Docs</h1>
|
|
||||||
<a href="/docs"
|
|
||||||
target="_blank">
|
|
||||||
<h4>Swagger</h4>
|
|
||||||
</a>
|
|
||||||
{% endblock %}
|
|
||||||
0
gallery/easel/route/view/common/utils/__init__.py
Normal file
53
gallery/easel/route/view/common/utils/template.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import datetime
|
||||||
|
import typing
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from babel.dates import format_date
|
||||||
|
from fastapi import Request
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
|
from gallery.easel.core import AppRequest
|
||||||
|
from gallery.version import __version__
|
||||||
|
|
||||||
|
from ...translation import _
|
||||||
|
from .tag import TagUtil
|
||||||
|
|
||||||
|
ContextProcessor = typing.Callable[[AppRequest], dict[str, typing.Any]]
|
||||||
|
|
||||||
|
|
||||||
|
def is_widget(request: Request) -> bool:
|
||||||
|
return (request.url.hostname and request.url.hostname.startswith("weather")) or (
|
||||||
|
request.query_params.get("widget") is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def base_context_processor(request: Request) -> dict:
|
||||||
|
return {
|
||||||
|
"is_widget": is_widget(request),
|
||||||
|
"provider": request.query_params.get("provider"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_templates(
|
||||||
|
templates_dir: Path | None = None, filters: dict | None = None, context_processor: ContextProcessor | None = None
|
||||||
|
) -> Jinja2Templates:
|
||||||
|
directory = [Path(__file__).parent.parent / "templates"]
|
||||||
|
if templates_dir:
|
||||||
|
directory.append(templates_dir)
|
||||||
|
context_processors: list[ContextProcessor] = [base_context_processor]
|
||||||
|
if context_processor:
|
||||||
|
context_processors.append(context_processor)
|
||||||
|
templates = Jinja2Templates(directory=directory, context_processors=context_processors)
|
||||||
|
templates.env.globals.update(
|
||||||
|
{
|
||||||
|
"_": _,
|
||||||
|
"version": __version__,
|
||||||
|
"format_date": format_date,
|
||||||
|
"datetime": datetime,
|
||||||
|
"tag_util": TagUtil,
|
||||||
|
"DATE_FORMAT": "E, d MMMM Y",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if filters:
|
||||||
|
templates.env.filters.update(filters)
|
||||||
|
return templates
|
||||||
@@ -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 "Телепрограмма"
|
|
||||||
36
gallery/easel/route/view/root/__init__.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Request
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
|
||||||
|
from ..common.utils.template import build_templates
|
||||||
|
|
||||||
|
|
||||||
|
class Section(NamedTuple):
|
||||||
|
link: str
|
||||||
|
title: str
|
||||||
|
icon: str
|
||||||
|
|
||||||
|
|
||||||
|
SECTIONS = [
|
||||||
|
Section("weather", "Weather", "brightness-high"),
|
||||||
|
Section("schedule", "TV program", "tv"),
|
||||||
|
]
|
||||||
|
|
||||||
|
base_dir = Path(__file__).parent
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
templates = build_templates(Path(__file__).parent / "templates")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_class=HTMLResponse)
|
||||||
|
async def get_section_list(request: Request):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request=request,
|
||||||
|
name="root_index.html",
|
||||||
|
context={
|
||||||
|
"sections": SECTIONS,
|
||||||
|
},
|
||||||
|
)
|
||||||
33
gallery/easel/route/view/root/templates/root_index.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}{{_("Index")}}{% endblock %}
|
||||||
|
{% block head %}
|
||||||
|
{{ super() }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{_("View")}}</h1>
|
||||||
|
<div class="list-group mb-3">
|
||||||
|
{% for section in sections %}
|
||||||
|
<app-link href="{{section.link}}"
|
||||||
|
icon="{{section.icon}}"
|
||||||
|
class="list-group-item list-group-item-action">
|
||||||
|
{{_(section.title)}}
|
||||||
|
</app-link>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<h1>{{_("Docs")}}</h1>
|
||||||
|
<div class="list-group mb-3">
|
||||||
|
<app-link href="/docs"
|
||||||
|
target="_blank"
|
||||||
|
icon="file-earmark-text"
|
||||||
|
class="list-group-item list-group-item-action">
|
||||||
|
Swagger UI
|
||||||
|
</app-link>
|
||||||
|
<app-link href="/redoc"
|
||||||
|
target="_blank"
|
||||||
|
icon="file-earmark-text"
|
||||||
|
class="list-group-item list-group-item-action">
|
||||||
|
ReDoc
|
||||||
|
</app-link>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -3,81 +3,66 @@ from pathlib import Path
|
|||||||
|
|
||||||
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 gallery.easel.core import AppRequest
|
from gallery.easel.core import AppRequest
|
||||||
from gallery.sketch.schedule.catalog import BUNDLE
|
from gallery.easel.depends.api import api_resolver
|
||||||
from gallery.version import __version__
|
from gallery.easel.depends.schedule import ScheduleApiDepends
|
||||||
|
from gallery.sketch.schedule.api import ScheduleApi
|
||||||
|
|
||||||
from ..common.util import TagType, TagUtil
|
from ..common.utils.tag import TagType, TagUtil
|
||||||
from ..translation import _
|
from ..common.utils.template import build_templates
|
||||||
from .filters import timedelta_format
|
from .filters import timedelta_format
|
||||||
|
|
||||||
base_dir = Path(__file__).parent
|
|
||||||
templates = Jinja2Templates(
|
def context_procesor(request: AppRequest) -> dict:
|
||||||
directory=[
|
return {
|
||||||
base_dir.parent / "common/templates",
|
"providers": request.app.state.api.get_api_providers(ScheduleApi),
|
||||||
base_dir / "templates",
|
}
|
||||||
]
|
|
||||||
|
|
||||||
|
templates = build_templates(
|
||||||
|
Path(__file__).parent / "templates",
|
||||||
|
{
|
||||||
|
"timedelta_format": timedelta_format,
|
||||||
|
},
|
||||||
|
context_procesor,
|
||||||
)
|
)
|
||||||
templates.env.globals.update({"_": _})
|
|
||||||
templates.env.filters["timedelta_format"] = timedelta_format
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter(prefix="/schedule")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/schedule", response_class=HTMLResponse)
|
@router.get("/", response_class=HTMLResponse)
|
||||||
async def get_schedule_list(request: AppRequest):
|
async def get_schedule_index(request: AppRequest, provider: str | None = None, query: str | None = None):
|
||||||
schedule_api = request.app.state.api.schedule
|
if query and provider:
|
||||||
channels = await schedule_api.get_channels()
|
schedule_api = api_resolver(ScheduleApi)(request, provider)
|
||||||
channels_data = BUNDLE.select_items(channels)
|
channels = await schedule_api.find_channels(query)
|
||||||
|
else:
|
||||||
|
channels = []
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request=request,
|
request=request,
|
||||||
name="index.html",
|
name="index.html",
|
||||||
context={
|
context={
|
||||||
"version": __version__,
|
"channels": channels,
|
||||||
"channels": channels_data,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/schedule/tag/{tag}", response_class=HTMLResponse)
|
@router.get("/{provider}/{channel}", response_class=RedirectResponse)
|
||||||
async def get_schedule_tag(request: AppRequest, tag: str, live: bool = False):
|
|
||||||
tag_value = TagUtil.parse_tag(tag)
|
|
||||||
schedule_api = request.app.state.api.schedule
|
|
||||||
results = await schedule_api.get_all_schedules(tag_value.date)
|
|
||||||
return templates.TemplateResponse(
|
|
||||||
request=request,
|
|
||||||
name="schedule.html",
|
|
||||||
context={
|
|
||||||
"version": __version__,
|
|
||||||
"tag_util": TagUtil,
|
|
||||||
"datetime": datetime,
|
|
||||||
"response": results[0],
|
|
||||||
"responses": results,
|
|
||||||
"live": live,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/schedule/{channel}", response_class=RedirectResponse)
|
|
||||||
async def get_channel_default(channel: str):
|
async def get_channel_default(channel: str):
|
||||||
return RedirectResponse(f"{channel}/tag/today")
|
return RedirectResponse(f"{channel}/tag/today")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/schedule/{channel}/tag/{tag}", response_class=HTMLResponse)
|
@router.get("/{provider}/{channel}/tag/{tag}", response_class=HTMLResponse)
|
||||||
async def get_channel_tag(request: AppRequest, channel: str, tag: str):
|
async def get_channel_tag(request: AppRequest, schedule_api: ScheduleApiDepends, channel: str, tag: str):
|
||||||
tag_value = TagUtil.parse_tag(tag)
|
tag_value = TagUtil.parse_tag(tag)
|
||||||
schedule_api = request.app.state.api.schedule
|
|
||||||
if tag_value.type == TagType.DAY:
|
if tag_value.type == TagType.DAY:
|
||||||
response = await schedule_api.get_channel_schedule(channel, tag_value.date)
|
response = await schedule_api.get_schedule(channel, tag_value.date)
|
||||||
else:
|
else:
|
||||||
raise ValueError(tag)
|
raise ValueError(tag)
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request=request,
|
request=request,
|
||||||
name="channel.html",
|
name="channel.html",
|
||||||
context={
|
context={
|
||||||
"version": __version__,
|
|
||||||
"tag_util": TagUtil,
|
"tag_util": TagUtil,
|
||||||
"datetime": datetime,
|
"datetime": datetime,
|
||||||
"response": response,
|
"response": response,
|
||||||
|
|||||||
@@ -1,37 +1,44 @@
|
|||||||
{% 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, DATE_FORMAT,
|
||||||
|
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 %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h4>
|
<h4>
|
||||||
<a class="button {{'disabled' if response.date == datetime.date.today() else ''}}"
|
<a class="icon-link {{'disabled' if response.date == datetime.date.today() else ''}}"
|
||||||
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️</a>
|
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">
|
||||||
<a class="button"
|
<i class="bi bi-arrow-left-square"></i>
|
||||||
href="../..">⬆️</a>
|
</a>
|
||||||
<span>{{response.channel.name}} | {{response.date.strftime('%a, %d %B %Y')}}</span>
|
<a class="icon-link"
|
||||||
<a class="button"
|
href="../../..">
|
||||||
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">➡️</a>
|
<i class="bi bi-arrow-up-square"></i>
|
||||||
|
</a>
|
||||||
|
<span>{{response.channel.name}} | {{format_date(response.date, DATE_FORMAT, locale=request.state.language)}}</span>
|
||||||
|
<a class="icon-link"
|
||||||
|
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">
|
||||||
|
<i class="bi bi-arrow-right-square"></i>
|
||||||
|
</a>
|
||||||
</h4>
|
</h4>
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for value in response.values %}
|
{% for value in response.values %}
|
||||||
<tr class="{{'table-success' if value.live else ''}}">
|
<tr class="{{'table-success' if value.live else ''}}">
|
||||||
<td>{{value.start.strftime('%H:%M')}}</td>
|
<td>
|
||||||
<td>{{(value.end - value.start) | timedelta_format}}</td>
|
<span>{{value.start.strftime('%H:%M')}}</span>
|
||||||
|
<span class="small ms-1">({{(value.end - value.start) | timedelta_format}})</span>
|
||||||
|
</td>
|
||||||
<td>{{value.label}}</td>
|
<td>{{value.label}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -3,16 +3,60 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{{_("TV program")}}</h1>
|
<h1>{{_("TV program")}}</h1>
|
||||||
<div class="list-group mb-5">
|
<form action=""
|
||||||
<a href="schedule/tag/today"
|
method="get"
|
||||||
class="list-group-item list-group-item-action px-4">
|
class="mb-4">
|
||||||
<span class="fw-bold">Все</span>
|
<div class="input-group mb-3">
|
||||||
</a>
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="query"
|
||||||
|
name="query"
|
||||||
|
placeholder="{{_('Enter the channel name')}}">
|
||||||
|
<input type="hidden"
|
||||||
|
class="form-control"
|
||||||
|
id="provider"
|
||||||
|
name="provider"
|
||||||
|
value="{{provider or providers[0]}}">
|
||||||
|
<button id="providerBtn"
|
||||||
|
class="btn btn-secondary dropdown-toggle"
|
||||||
|
type="text"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false">{{provider or providers[0]}}</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
{% for provider in providers %}
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item"
|
||||||
|
onclick="provider.value='{{provider}}'; providerBtn.innerText='{{provider}}'">{{provider}}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<button class="btn btn-primary"
|
||||||
|
type="submit">{{_("Search")}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% if channels %}
|
||||||
|
<ul id="channels"
|
||||||
|
class="list-group mb-5">
|
||||||
{% for channel in channels %}
|
{% for channel in channels %}
|
||||||
<a href="schedule/{{channel.id}}"
|
<schedule-channel channel="{{channel.model_dump() | tojson | forceescape}}"></schedule-channel>
|
||||||
class="list-group-item list-group-item-action px-4">
|
|
||||||
<span class="text-primary">{{channel.name}}</span>
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</ul>
|
||||||
|
<hr>
|
||||||
|
{% endif %}
|
||||||
|
<ul id="storedChannels"
|
||||||
|
class="list-group mb-5">
|
||||||
|
</ul>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const searchQuery = params.get('query');
|
||||||
|
if (searchQuery) {
|
||||||
|
document.querySelector('#query').value = searchQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", (event) => {
|
||||||
|
scheduleChannelManager.loadChannels('#storedChannels');
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block title %}
|
|
||||||
{{'Прямые трансляции' if live else _("TV program")}} | {{response.date.strftime('%a, %d %B %Y')}}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block header %}
|
|
||||||
<span class="fs-4 text-body ms-2 me-2">/</span>
|
|
||||||
<app-link href="/schedule"
|
|
||||||
icon="tv">{{_("TV program")}}</app-link>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h4>
|
|
||||||
<a class="button {{'disabled' if response.date == datetime.date.today() else ''}}"
|
|
||||||
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>
|
|
||||||
<a class="button"
|
|
||||||
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">➡️</a>
|
|
||||||
</h4>
|
|
||||||
<div>
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for response in responses %}
|
|
||||||
{% set values = (response.values|selectattr('live') if live else response.values)|list %}
|
|
||||||
{% if values|length > 0 %}
|
|
||||||
<tr class="table-primary fs-4">
|
|
||||||
<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 ''}}">
|
|
||||||
<td>{{value.start.strftime('%H:%M')}}</td>
|
|
||||||
<td>{{(value.end - value.start) | timedelta_format}}</td>
|
|
||||||
<td>{{value.label}}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
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
|
||||||
|
|
||||||
_translation: ContextVar[gettext.GNUTranslations | gettext.NullTranslations] = (
|
from gallery.util import root_path
|
||||||
ContextVar("translation")
|
|
||||||
)
|
_translation: ContextVar[gettext.GNUTranslations | gettext.NullTranslations] = ContextVar("translation")
|
||||||
|
|
||||||
|
|
||||||
async def set_language(
|
async def set_language(
|
||||||
@@ -18,9 +17,7 @@ async def set_language(
|
|||||||
lang = language or accept_language.split(",")[0].split("-")[0]
|
lang = language or accept_language.split(",")[0].split("-")[0]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
t = gettext.translation(
|
t = gettext.translation("messages", localedir=root_path / "locales", languages=[lang])
|
||||||
"messages", localedir=Path(__file__).parent / "locales", languages=[lang]
|
|
||||||
)
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
t = gettext.NullTranslations()
|
t = gettext.NullTranslations()
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,34 @@
|
|||||||
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 gallery.easel.core import AppRequest
|
from gallery.easel.core import AppRequest
|
||||||
|
from gallery.easel.depends.api import api_resolver
|
||||||
|
from gallery.easel.depends.weather import WeatherApiDepends
|
||||||
|
from gallery.sketch.weather.api import WeatherApi
|
||||||
from gallery.sketch.weather.model import WeatherResponse
|
from gallery.sketch.weather.model import WeatherResponse
|
||||||
from gallery.version import __version__
|
|
||||||
|
|
||||||
from ..common.util import TagType, TagUtil
|
from ..common.utils.tag import TagType, TagUtil
|
||||||
from ..translation import _
|
from ..common.utils.template import build_templates
|
||||||
from .filters import cloudness_icon, wind_direction_icon
|
from .filters import cloudness_icon, wind_direction_icon
|
||||||
|
|
||||||
|
|
||||||
base_dir = Path(__file__).parent
|
def context_procesor(request: AppRequest) -> dict:
|
||||||
templates = Jinja2Templates(
|
return {
|
||||||
directory=[
|
"providers": request.app.state.api.get_api_providers(WeatherApi),
|
||||||
base_dir.parent / "common/templates",
|
}
|
||||||
base_dir / "templates",
|
|
||||||
]
|
|
||||||
|
templates = build_templates(
|
||||||
|
Path(__file__).parent / "templates",
|
||||||
|
{
|
||||||
|
"wind_direction_icon": wind_direction_icon,
|
||||||
|
"cloudness_icon": cloudness_icon,
|
||||||
|
},
|
||||||
|
context_procesor,
|
||||||
)
|
)
|
||||||
templates.env.globals.update({"_": _})
|
|
||||||
templates.env.filters["wind_direction_icon"] = wind_direction_icon
|
|
||||||
templates.env.filters["cloudness_icon"] = cloudness_icon
|
|
||||||
|
|
||||||
|
|
||||||
def build_weather_response(request: AppRequest, response: WeatherResponse):
|
def build_weather_response(request: AppRequest, response: WeatherResponse):
|
||||||
@@ -32,52 +36,55 @@ 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,
|
|
||||||
"datetime": datetime,
|
|
||||||
"response": response,
|
"response": response,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
@router.get("/weather", response_class=HTMLResponse)
|
router = APIRouter(prefix="/weather")
|
||||||
async def get_weather_index(request: AppRequest, query: str | None = None):
|
|
||||||
weather_api = request.app.state.api.weather
|
|
||||||
locations = (await weather_api.find_locations(query)) if query else []
|
@router.get("/", response_class=HTMLResponse)
|
||||||
|
async def get_weather_index(request: AppRequest, provider: str | None = None, query: str | None = None):
|
||||||
|
if query and provider:
|
||||||
|
weather_api = api_resolver(WeatherApi)(request, provider)
|
||||||
|
locations = await weather_api.find_locations(query)
|
||||||
|
else:
|
||||||
|
locations = []
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request=request,
|
request=request,
|
||||||
name="index.html",
|
name="index.html",
|
||||||
context={
|
context={
|
||||||
"version": __version__,
|
|
||||||
"locations": locations,
|
"locations": locations,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/weather/{location}", response_class=RedirectResponse)
|
@router.get("/{provider}/{location}", response_class=RedirectResponse)
|
||||||
async def get_weather_default(location: str):
|
async def get_weather(location: str):
|
||||||
return RedirectResponse(f"{location}/tag/today")
|
return RedirectResponse(f"{location}/tag/today")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/weather/{location}/day/{date}", response_class=HTMLResponse)
|
@router.get("/{provider}/{location}/day/{date}", response_class=HTMLResponse)
|
||||||
async def get_weather_day(request: AppRequest, location: str, date: datetime.date):
|
async def get_weather_day(
|
||||||
weather_api = request.app.state.api.weather
|
request: AppRequest,
|
||||||
|
weather_api: WeatherApiDepends,
|
||||||
|
location: str,
|
||||||
|
date: datetime.date,
|
||||||
|
):
|
||||||
response = await weather_api.get_day(location, date)
|
response = await weather_api.get_day(location, date)
|
||||||
return build_weather_response(request, response)
|
return build_weather_response(request, response)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/weather/{location}/days/{days}", response_class=HTMLResponse)
|
@router.get("/{provider}/{location}/days/{days}", response_class=HTMLResponse)
|
||||||
async def get_weather_days(request: AppRequest, location: str, days: int):
|
async def get_weather_days(request: AppRequest, weather_api: WeatherApiDepends, location: str, days: int):
|
||||||
weather_api = request.app.state.api.weather
|
|
||||||
response = await weather_api.get_days(location, days)
|
response = await weather_api.get_days(location, days)
|
||||||
return build_weather_response(request, response)
|
return build_weather_response(request, response)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/weather/{location}/tag/{tag}", response_class=HTMLResponse)
|
@router.get("/{provider}/{location}/tag/{tag}", response_class=HTMLResponse)
|
||||||
async def get_weather_tag(request: AppRequest, location: str, tag: str):
|
async def get_weather_tag(request: AppRequest, weather_api: WeatherApiDepends, location: str, tag: str):
|
||||||
tag_value = TagUtil.parse_tag(tag)
|
tag_value = TagUtil.parse_tag(tag)
|
||||||
weather_api = request.app.state.api.weather
|
|
||||||
if tag_value.type == TagType.DAY:
|
if tag_value.type == TagType.DAY:
|
||||||
response = await weather_api.get_day(location, tag_value.date)
|
response = await weather_api.get_day(location, tag_value.date)
|
||||||
elif tag_value.type == TagType.DAYS:
|
elif tag_value.type == TagType.DAYS:
|
||||||
@@ -85,3 +92,10 @@ async def get_weather_tag(request: AppRequest, location: str, tag: str):
|
|||||||
else:
|
else:
|
||||||
raise ValueError(tag)
|
raise ValueError(tag)
|
||||||
return build_weather_response(request, response)
|
return build_weather_response(request, response)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/zmiyevka-184640/tag/{tag}", response_class=RedirectResponse)
|
||||||
|
async def get_zmiyevka_weather_compat(request: AppRequest):
|
||||||
|
path_parts = request.url.path.split("/")
|
||||||
|
path_parts.insert(2, "gismeteo")
|
||||||
|
return RedirectResponse("/".join(path_parts))
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
from gallery.sketch.weather.model import (
|
from gallery.sketch.weather.model import (
|
||||||
Cloudness,
|
Cloudness,
|
||||||
Precipitation,
|
Precipitation,
|
||||||
@@ -9,40 +11,46 @@ from gallery.sketch.weather.model import (
|
|||||||
|
|
||||||
def wind_direction_icon(wind_direction_deg: float) -> str:
|
def wind_direction_icon(wind_direction_deg: float) -> str:
|
||||||
wind_direction = WindDirectionDeg(wind_direction_deg).direction
|
wind_direction = WindDirectionDeg(wind_direction_deg).direction
|
||||||
return {
|
if wind_direction == WindDirection.CALM:
|
||||||
WindDirection.N: "⬇️",
|
return "wind-calm"
|
||||||
WindDirection.NE: "↙️",
|
else:
|
||||||
WindDirection.E: "⬅️",
|
return f"wind-from-{wind_direction.name.lower()}"
|
||||||
WindDirection.SE: "↖️",
|
|
||||||
WindDirection.S: "⬆️",
|
|
||||||
WindDirection.SW: "↗️",
|
|
||||||
WindDirection.W: "➡️",
|
|
||||||
WindDirection.NW: "↘️",
|
|
||||||
WindDirection.CALM: "",
|
|
||||||
}.get(wind_direction, wind_direction)
|
|
||||||
|
|
||||||
|
|
||||||
def cloudness_icon(sky: Sky) -> list[str]:
|
def cloudness_icon(sky: Sky, date: datetime.datetime, period: str) -> list[str]:
|
||||||
|
day = (3 < date.hour < 22) if period == "day" else True
|
||||||
|
day_prefix = "day" if day else "night-alt"
|
||||||
main_icon = ""
|
main_icon = ""
|
||||||
if sky.thunder:
|
if sky.thunder:
|
||||||
if sky.cloudness == Cloudness.CLEAR:
|
main_icon = {
|
||||||
main_icon = "🌩️"
|
Precipitation.NO: "lightning",
|
||||||
if sky.precipitation == Precipitation.NO:
|
Precipitation.SMALL_RAIN: "storm-showers",
|
||||||
main_icon = "⚡"
|
Precipitation.RAIN: "thunderstorm",
|
||||||
else:
|
Precipitation.HEAVY_RAIN: "thunderstorm",
|
||||||
main_icon = "⛈️"
|
Precipitation.SHOWER: "thunderstorm",
|
||||||
|
Precipitation.SNOW: "storm-showers",
|
||||||
|
Precipitation.HEAVY_SNOW: "storm-showers",
|
||||||
|
}[sky.precipitation]
|
||||||
|
if sky.cloudness == Cloudness.PARTLY_CLOUDY:
|
||||||
|
main_icon = f"{day_prefix}-{main_icon}"
|
||||||
elif sky.precipitation == Precipitation.NO:
|
elif sky.precipitation == Precipitation.NO:
|
||||||
main_icon = {
|
main_icon = {
|
||||||
Cloudness.CLEAR: "☀️",
|
Cloudness.CLEAR: "day-sunny" if day else "night-clear",
|
||||||
Cloudness.PARTLY_CLOUDY: "🌤️",
|
Cloudness.PARTLY_CLOUDY: f"{day_prefix}-cloudy",
|
||||||
Cloudness.CLOUDY: "⛅",
|
Cloudness.CLOUDY: "cloud",
|
||||||
Cloudness.MAINLY_CLOUDY: "☁️",
|
Cloudness.MAINLY_CLOUDY: "cloudy",
|
||||||
}[sky.cloudness]
|
}[sky.cloudness]
|
||||||
elif sky.precipitation in [Precipitation.SNOW, Precipitation.HEAVY_SNOW]:
|
|
||||||
main_icon = "🌨️"
|
|
||||||
else:
|
else:
|
||||||
main_icon = "🌧️"
|
main_icon = {
|
||||||
|
Precipitation.SMALL_RAIN: "showers",
|
||||||
|
Precipitation.RAIN: "rain-mix",
|
||||||
|
Precipitation.HEAVY_RAIN: "rain",
|
||||||
|
Precipitation.SHOWER: "rain",
|
||||||
|
Precipitation.SNOW: "snow",
|
||||||
|
Precipitation.HEAVY_SNOW: "snow",
|
||||||
|
Precipitation.HAIL: "hail",
|
||||||
|
}[sky.precipitation]
|
||||||
|
if sky.cloudness == Cloudness.PARTLY_CLOUDY:
|
||||||
|
main_icon = f"{day_prefix}-{main_icon}"
|
||||||
icons = [main_icon]
|
icons = [main_icon]
|
||||||
if sky.fog:
|
|
||||||
icons.append("🌫️")
|
|
||||||
return icons
|
return icons
|
||||||
|
|||||||
@@ -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,63 +11,52 @@
|
|||||||
class="form-control"
|
class="form-control"
|
||||||
id="query"
|
id="query"
|
||||||
name="query"
|
name="query"
|
||||||
placeholder="Enter the city name">
|
placeholder="{{_('Enter the city name')}}">
|
||||||
|
<input type="hidden"
|
||||||
|
class="form-control"
|
||||||
|
id="provider"
|
||||||
|
name="provider"
|
||||||
|
value="{{provider or providers[0]}}">
|
||||||
|
<button id="providerBtn"
|
||||||
|
class="btn btn-secondary dropdown-toggle"
|
||||||
|
type="text"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false">{{provider or providers[0]}}</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
{% for provider in providers %}
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item"
|
||||||
|
onclick="provider.value='{{provider}}'; providerBtn.innerText='{{provider}}'">{{provider}}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
<button class="btn btn-primary"
|
<button class="btn btn-primary"
|
||||||
type="submit">Search</button>
|
type="submit">{{_("Search")}}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
{% if locations %}
|
||||||
<ul id="locations"
|
<ul id="locations"
|
||||||
class="list-group mb-5">
|
class="list-group mb-5">
|
||||||
{% for location in locations %}
|
{% for location in locations %}
|
||||||
<a href="weather/{{location.id}}"
|
<weather-location location="{{location.model_dump() | tojson | forceescape}}"></weather-location>
|
||||||
class="list-group-item list-group-item-action px-4"
|
|
||||||
onclick="saveLocation({id:'{{location.id}}', name:'{{location.name}}'});">
|
|
||||||
<span class="text-primary">{{location.name}}</span>
|
|
||||||
<span class="small ms-1 text-secondary">
|
|
||||||
{{location.country}}, {{location.district}}, {{location.subdistrict}}
|
|
||||||
</span>
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
<hr>
|
||||||
|
{% endif %}
|
||||||
|
<ul id="storedLocations"
|
||||||
|
class="list-group mb-5">
|
||||||
|
</ul>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
document.loadLocations = () => {
|
|
||||||
const locations = JSON.parse(window.localStorage.getItem('locations') || '{}');
|
|
||||||
const container = document.querySelector('#locations');
|
|
||||||
container.innerHTML = '';
|
|
||||||
for (const [id, name] of Object.entries(locations)) {
|
|
||||||
const element = document.createElement('a');
|
|
||||||
element.href = `weather/${id}`;
|
|
||||||
element.className = 'list-group-item list-group-item-action px-4 d-flex justify-content-between align-items-start';
|
|
||||||
element.innerHTML = `
|
|
||||||
<span class="text-primary me-auto">${name}</span>
|
|
||||||
<span class="text-danger" onclick="removeLocation('${id}'); event.preventDefault();">✕</span>
|
|
||||||
`;
|
|
||||||
container.appendChild(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.saveLocation = (location) => {
|
|
||||||
const locations = JSON.parse(window.localStorage.getItem('locations') || '{}');
|
|
||||||
locations[location.id] = location.name;
|
|
||||||
window.localStorage.setItem('locations', JSON.stringify(locations));
|
|
||||||
}
|
|
||||||
|
|
||||||
document.removeLocation = (id) => {
|
|
||||||
const locations = JSON.parse(window.localStorage.getItem('locations') || '{}');
|
|
||||||
delete locations[id];
|
|
||||||
window.localStorage.setItem('locations', JSON.stringify(locations));
|
|
||||||
document.loadLocations();
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const searchQuery = params.get('query');
|
const searchQuery = params.get('query');
|
||||||
if (searchQuery) {
|
if (searchQuery) {
|
||||||
document.querySelector('#query').value = searchQuery;
|
document.querySelector('#query').value = searchQuery;
|
||||||
} else {
|
|
||||||
document.loadLocations();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", (event) => {
|
||||||
|
weatherLocationManager.loadLocations('#storedLocations');
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -2,23 +2,31 @@
|
|||||||
{% 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 %}
|
||||||
<h4>
|
<h4>
|
||||||
{% if response.period == 'day' %}
|
{% if response.period == 'day' %}
|
||||||
<a class="button {{'disabled' if response.date == datetime.date.today() else ''}}"
|
<a class="icon-link {{'disabled' if response.date == datetime.date.today() else ''}}"
|
||||||
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️</a>
|
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">
|
||||||
<a class="button"
|
<i class="bi bi-arrow-left-square"></i>
|
||||||
href="../tag/days-10">⬆️</a>
|
</a>
|
||||||
<span>{{response.location}} | {{response.date.strftime('%a, %d %B %Y')}}</span>
|
<a class="icon-link"
|
||||||
<a class="button"
|
href="../tag/days-10">
|
||||||
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">➡️</a>
|
<i class="bi bi-arrow-up-square"></i>
|
||||||
|
</a>
|
||||||
|
<span>{{response.location}} | {{format_date(response.date, DATE_FORMAT, locale=request.state.language)}}</span>
|
||||||
|
<a class="icon-link"
|
||||||
|
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">
|
||||||
|
<i class="bi bi-arrow-right-square"></i>
|
||||||
|
</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, DATE_FORMAT, locale=request.state.language)}}</span>
|
||||||
|
<span>- {{format_date(response.date + datetime.timedelta(days=(response.values | length - 1)), DATE_FORMAT,
|
||||||
|
locale=request.state.language)}}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@@ -36,9 +44,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,14 +57,16 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="{{response.values | length}}"
|
<td colspan="{{response.values | length}}"
|
||||||
class="header">
|
class="header">
|
||||||
Облачность
|
{{_("Cloudiness")}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
{% for value in response.values %}
|
{% for value in response.values %}
|
||||||
<td class="cloudness">
|
<td class="cloudness"
|
||||||
{% for icon in value.sky | cloudness_icon %}
|
data-bs-toggle="tooltip"
|
||||||
<div class="icon">{{icon}}</div>
|
data-bs-title="{{ value.sky }}">
|
||||||
|
{% for icon in value.sky | cloudness_icon(value.date, response.period) %}
|
||||||
|
<div class="wi wi-{{icon}} wi-xl text-primary"></div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -65,7 +75,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,13 +94,15 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="{{response.values | length}}"
|
<td colspan="{{response.values | length}}"
|
||||||
class="header">
|
class="header">
|
||||||
Направление ветра
|
{{_("Wind direction")}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
{% for value in response.values %}
|
{% for value in response.values %}
|
||||||
<td class="wind">
|
<td class="wind"
|
||||||
<span class="icon">{{value.wind_direction | wind_direction_icon}}</span>
|
data-bs-toggle="tooltip"
|
||||||
|
data-bs-title="{{ value.wind_direction }}">
|
||||||
|
<div class="wi wi-wind-deg wi-{{value.wind_direction | wind_direction_icon}} wi-l text-primary"></div>
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
@@ -98,7 +110,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,14 +130,14 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="{{response.values | length}}"
|
<td colspan="{{response.values | length}}"
|
||||||
class="header">
|
class="header">
|
||||||
Осадки, мм
|
{{_("Precipitation, mm")}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
{% for value in response.values %}
|
{% for value in response.values %}
|
||||||
<td class="precipitation"
|
<td class="precipitation"
|
||||||
style="background-color: rgba(0, 128, 255, {{value.precipitation * 0.1}});">
|
style="background-color: rgba(0, 128, 255, {{value.precipitation * 0.1}});">
|
||||||
<span class="value">{{value.precipitation or ' '}}</span>
|
<span class="value">{{(value.precipitation | round(2)) if value.precipitation else ' '}}</span>
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
@@ -133,7 +145,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 +163,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="{{response.values | length}}"
|
<td colspan="{{response.values | length}}"
|
||||||
class="header">
|
class="header">
|
||||||
Влажность, %
|
{{_("Humidity, %")}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import logging
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from fastapi import HTTPException, status
|
||||||
|
|
||||||
from gallery.sketch.source import ApiSource
|
from gallery.sketch.source import ApiSource
|
||||||
from gallery.sketch.weather.api import WeatherApi
|
from gallery.sketch.weather.api import WeatherApi
|
||||||
@@ -84,16 +85,29 @@ class GismeteoApi(WeatherApi):
|
|||||||
Location(
|
Location(
|
||||||
id=f"{item['slug']}-{item['id']}",
|
id=f"{item['slug']}-{item['id']}",
|
||||||
name=item["translations"]["kk"]["city"]["name"],
|
name=item["translations"]["kk"]["city"]["name"],
|
||||||
|
provider=self.provider,
|
||||||
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"],
|
||||||
district=item["translations"]["kk"]["district"]["name"],
|
country_code=item["country"]["code"].lower(),
|
||||||
subdistrict=item["translations"]["kk"]["subdistrict"]["name"],
|
district=(
|
||||||
|
item["translations"]["kk"]["district"]["name"] if item["translations"]["kk"]["district"] else ""
|
||||||
|
),
|
||||||
|
subdistrict=(
|
||||||
|
item["translations"]["kk"]["subdistrict"]["name"]
|
||||||
|
if "subdistrict" in item["translations"]["kk"]
|
||||||
|
else ""
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def get_day(self, location_id: str, date: datetime.date) -> WeatherResponse:
|
async def get_day(self, location_id: str, date: datetime.date) -> WeatherResponse:
|
||||||
|
max_date = datetime.date.today() + datetime.timedelta(days=9)
|
||||||
|
if date > max_date:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST, detail={"max_date": max_date.strftime("%Y-%m-%d")}
|
||||||
|
)
|
||||||
data = await self.SOURCE.request(f"weather-{location_id}/{datehelp.dump(date)}")
|
data = await self.SOURCE.request(f"weather-{location_id}/{datehelp.dump(date)}")
|
||||||
return self._parse_oneday(date, data)
|
return self._parse_oneday(date, data)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from bs4 import Tag
|
|||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class WidgetParser:
|
class WidgetParser:
|
||||||
def parse_widget(self, tag: Tag) -> Tag:
|
def parse_widget(self, tag: Tag) -> Tag:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
@@ -15,6 +16,8 @@ from gallery.sketch.weather.model import (
|
|||||||
|
|
||||||
from .core import BaseWidgetParser, RowParser
|
from .core import BaseWidgetParser, RowParser
|
||||||
|
|
||||||
|
logger = logging.getLogger("gismeteo")
|
||||||
|
|
||||||
ONE_DAY_PARSER = BaseWidgetParser(".widget.widget-oneday .widget-items")
|
ONE_DAY_PARSER = BaseWidgetParser(".widget.widget-oneday .widget-items")
|
||||||
DAYS_PARSER = BaseWidgetParser(".widget.widget-days .widget-items")
|
DAYS_PARSER = BaseWidgetParser(".widget.widget-days .widget-items")
|
||||||
|
|
||||||
@@ -36,16 +39,11 @@ class DateParser(RowParser[datetime.datetime]):
|
|||||||
KEY = "date"
|
KEY = "date"
|
||||||
|
|
||||||
def parse_row(self, tag: Tag) -> Iterable[datetime.datetime]:
|
def parse_row(self, tag: Tag) -> Iterable[datetime.datetime]:
|
||||||
datetime_date_tag = tag.select_one(
|
datetime_time_row = tag.select_one(".widget-row.widget-row-datetime-time")
|
||||||
".widget-row.widget-row-datetime-date > .row-item"
|
if datetime_time_row:
|
||||||
)
|
for item in datetime_time_row.select(".row-item > time-value"):
|
||||||
if datetime_date_tag:
|
timestamp = int(item.attrs["timestamp"])
|
||||||
date_str = datetime_date_tag.find(text=True, recursive=False).text
|
time = datetime.datetime.fromtimestamp(timestamp)
|
||||||
date = dateparser.parse(date_str, languages=["ru"])
|
|
||||||
for item in tag.select(".widget-row.widget-row-datetime-time > .row-item"):
|
|
||||||
time_str = item.text
|
|
||||||
time = dateparser.parse(time_str, languages=["ru"])
|
|
||||||
time = time.replace(year=date.year, month=date.month, day=date.day)
|
|
||||||
yield time
|
yield time
|
||||||
else:
|
else:
|
||||||
for item in tag.select(".widget-row.widget-row-date > .row-item"):
|
for item in tag.select(".widget-row.widget-row-date > .row-item"):
|
||||||
@@ -59,6 +57,7 @@ class SkyParser(RowParser[Sky]):
|
|||||||
|
|
||||||
CLOUDNESS_MAP: dict[str, Cloudness] = {
|
CLOUDNESS_MAP: dict[str, Cloudness] = {
|
||||||
"ясно": Cloudness.CLEAR,
|
"ясно": Cloudness.CLEAR,
|
||||||
|
"безоблачно": Cloudness.CLEAR,
|
||||||
"малооблачно": Cloudness.PARTLY_CLOUDY,
|
"малооблачно": Cloudness.PARTLY_CLOUDY,
|
||||||
"облачно": Cloudness.CLOUDY,
|
"облачно": Cloudness.CLOUDY,
|
||||||
"пасмурно": Cloudness.MAINLY_CLOUDY,
|
"пасмурно": Cloudness.MAINLY_CLOUDY,
|
||||||
@@ -66,36 +65,51 @@ class SkyParser(RowParser[Sky]):
|
|||||||
|
|
||||||
PRECIPITATION_MAP: dict[str, Precipitation] = {
|
PRECIPITATION_MAP: dict[str, Precipitation] = {
|
||||||
"без осадков": Precipitation.NO,
|
"без осадков": Precipitation.NO,
|
||||||
"небольшой дождь": Precipitation.SMALL_RAIN,
|
"небольшой дождь": Precipitation.SMALL_RAIN,
|
||||||
"сильный дождь": Precipitation.HEAVY_RAIN,
|
"сильный дождь": Precipitation.HEAVY_RAIN,
|
||||||
|
"ливневый дождь": Precipitation.SHOWER,
|
||||||
"дождь": Precipitation.RAIN,
|
"дождь": Precipitation.RAIN,
|
||||||
"ливень": Precipitation.SHOWER,
|
"ливень": Precipitation.SHOWER,
|
||||||
"снег": Precipitation.SNOW,
|
"снег": Precipitation.SNOW,
|
||||||
"небольшой снег": Precipitation.SNOW,
|
"небольшой снег": Precipitation.SNOW,
|
||||||
"сильный снег": Precipitation.HEAVY_SNOW,
|
"сильный снег": Precipitation.HEAVY_SNOW,
|
||||||
"мокрый снег": Precipitation.SNOW,
|
"мокрый снег": Precipitation.SNOW,
|
||||||
"снег с дождём": Precipitation.SNOW,
|
"снег с дождём": Precipitation.SNOW,
|
||||||
"сильный снег с дождём": Precipitation.HEAVY_SNOW,
|
"сильный снег с дождём": Precipitation.HEAVY_SNOW,
|
||||||
"небольшой снег с дождём": Precipitation.SNOW,
|
"небольшой снег с дождём": Precipitation.SNOW,
|
||||||
"небольшой мокрый снег": Precipitation.SNOW,
|
"небольшой мокрый снег": Precipitation.SNOW,
|
||||||
|
"град": Precipitation.HAIL,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
THUNDER = "гроза"
|
||||||
|
FOG = "дымка"
|
||||||
|
|
||||||
def parse_row(self, tag: Tag) -> Iterable[Sky]:
|
def parse_row(self, tag: Tag) -> Iterable[Sky]:
|
||||||
for item in tag.select(".widget-row[data-row=icon-tooltip] > .row-item"):
|
for item in tag.select(".widget-row[data-row=icon-tooltip] > .row-item"):
|
||||||
sky_str = item.attrs["data-tooltip"]
|
sky_str = item.attrs["data-tooltip"]
|
||||||
values = {item.strip().lower() for item in sky_str.split(",")}
|
values = {item.strip().lower() for item in sky_str.split(",")}
|
||||||
cloudness = Cloudness.CLEAR
|
cloudness = Cloudness.CLEAR
|
||||||
precipitation = Precipitation.NO
|
precipitation = Precipitation.NO
|
||||||
thunder = "гроза" in values
|
thunder = False
|
||||||
fog = "дымка" in values
|
fog = False
|
||||||
|
if self.THUNDER in values:
|
||||||
|
thunder = True
|
||||||
|
values.remove(self.THUNDER)
|
||||||
|
if self.FOG in values:
|
||||||
|
fog = True
|
||||||
|
values.remove(self.FOG)
|
||||||
for k, v in self.CLOUDNESS_MAP.items():
|
for k, v in self.CLOUDNESS_MAP.items():
|
||||||
if k in values:
|
if k in values:
|
||||||
cloudness = v
|
cloudness = v
|
||||||
|
values.remove(k)
|
||||||
break
|
break
|
||||||
for k, v in self.PRECIPITATION_MAP.items():
|
for k, v in self.PRECIPITATION_MAP.items():
|
||||||
if k in values:
|
if k in values:
|
||||||
precipitation = v
|
precipitation = v
|
||||||
|
values.remove(k)
|
||||||
break
|
break
|
||||||
|
if values:
|
||||||
|
logger.warning("unknown sky values: %s:", values)
|
||||||
yield Sky(
|
yield Sky(
|
||||||
cloudness=cloudness,
|
cloudness=cloudness,
|
||||||
precipitation=precipitation,
|
precipitation=precipitation,
|
||||||
@@ -108,21 +122,15 @@ class TemperatureParser(RowParser[list[int]]):
|
|||||||
KEY = "temperature"
|
KEY = "temperature"
|
||||||
|
|
||||||
def parse_row(self, tag: Tag) -> Iterable[list[int]]:
|
def parse_row(self, tag: Tag) -> Iterable[list[int]]:
|
||||||
for item in tag.select(
|
for item in tag.select(".widget-row-chart[data-row=temperature-air] > .chart > .values > .value"):
|
||||||
".widget-row-chart[data-row=temperature-air] > .chart > .values > .value"
|
yield [int(value.attrs["value"]) for value in item.select("temperature-value")]
|
||||||
):
|
|
||||||
yield [
|
|
||||||
int(value.attrs["value"]) for value in item.select("temperature-value")
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class WindSpeedParser(RowParser[int]):
|
class WindSpeedParser(RowParser[int]):
|
||||||
KEY = "wind_speed"
|
KEY = "wind_speed"
|
||||||
|
|
||||||
def parse_row(self, tag: Tag) -> Iterable[int]:
|
def parse_row(self, tag: Tag) -> Iterable[int]:
|
||||||
for item in tag.select(
|
for item in tag.select(".widget-row-wind > .row-item > .wind-speed > speed-value"):
|
||||||
".widget-row-wind > .row-item > .wind-speed > speed-value"
|
|
||||||
):
|
|
||||||
yield int(item.attrs["value"])
|
yield int(item.attrs["value"])
|
||||||
|
|
||||||
|
|
||||||
@@ -140,6 +148,7 @@ class WindDirectionParser(RowParser[WindDirection]):
|
|||||||
|
|
||||||
WIND_DIRECTION_MAP: dict[str, WindDirection] = {
|
WIND_DIRECTION_MAP: dict[str, WindDirection] = {
|
||||||
"—": WindDirection.CALM,
|
"—": WindDirection.CALM,
|
||||||
|
"": WindDirection.CALM,
|
||||||
"штиль": WindDirection.CALM,
|
"штиль": WindDirection.CALM,
|
||||||
"с": WindDirection.N,
|
"с": WindDirection.N,
|
||||||
"св": WindDirection.NE,
|
"св": WindDirection.NE,
|
||||||
@@ -152,32 +161,28 @@ class WindDirectionParser(RowParser[WindDirection]):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def parse_row(self, tag: Tag) -> Iterable[float]:
|
def parse_row(self, tag: Tag) -> Iterable[float]:
|
||||||
for item in tag.select(
|
for item in tag.select(".widget-row-wind > .row-item > .wind-speed > .wind-direction"):
|
||||||
".widget-row-wind > .row-item > .wind-speed > .wind-direction"
|
|
||||||
):
|
|
||||||
wind_direction_str = item.text.lower().strip()
|
wind_direction_str = item.text.lower().strip()
|
||||||
yield WindDirectionDeg.from_direction(
|
yield WindDirectionDeg.from_direction(self.WIND_DIRECTION_MAP[wind_direction_str]).value
|
||||||
self.WIND_DIRECTION_MAP[wind_direction_str]
|
|
||||||
).value
|
|
||||||
|
|
||||||
|
|
||||||
class PrecipitationParser(RowParser[float]):
|
class PrecipitationParser(RowParser[float]):
|
||||||
KEY = "precipitation"
|
KEY = "precipitation"
|
||||||
|
|
||||||
def parse_row(self, tag: Tag) -> Iterable[float]:
|
def parse_row(self, tag: Tag) -> Iterable[float]:
|
||||||
for item in tag.select(
|
for item in tag.select(".widget-row[data-row=precipitation-bars] > .row-item"):
|
||||||
".widget-row[data-row=precipitation-bars] > .row-item > .item-unit"
|
value = item.select_one("precipitation-value")
|
||||||
):
|
if value:
|
||||||
yield float(item.text.replace(",", "."))
|
yield float(value.attrs["value"])
|
||||||
|
else:
|
||||||
|
yield 0
|
||||||
|
|
||||||
|
|
||||||
class PressureParser(RowParser[list[int]]):
|
class PressureParser(RowParser[list[int]]):
|
||||||
KEY = "pressure"
|
KEY = "pressure"
|
||||||
|
|
||||||
def parse_row(self, tag: Tag) -> Iterable[list[int]]:
|
def parse_row(self, tag: Tag) -> Iterable[list[int]]:
|
||||||
for item in tag.select(
|
for item in tag.select(".widget-row-chart[data-row=pressure] > .chart > .values > .value"):
|
||||||
".widget-row-chart[data-row=pressure] > .chart > .values > .value"
|
|
||||||
):
|
|
||||||
yield [int(value.attrs["value"]) for value in item.select("pressure-value")]
|
yield [int(value.attrs["value"]) for value in item.select("pressure-value")]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
|
|
||||||
from gallery.sketch.schedule.api import ScheduleApi
|
from gallery.sketch.schedule.api import ScheduleApi
|
||||||
from gallery.sketch.schedule.model import Channel, ChannelId, Schedule, ScheduleValue
|
from gallery.sketch.schedule.model import Channel, Schedule, ScheduleValue
|
||||||
from gallery.sketch.source import ApiSource
|
from gallery.sketch.source import ApiSource
|
||||||
|
|
||||||
logger = logging.getLogger("matchtv")
|
logger = logging.getLogger("matchtv")
|
||||||
@@ -14,43 +13,30 @@ class MatchTvApi(ScheduleApi):
|
|||||||
PROVIDER = "matchtv"
|
PROVIDER = "matchtv"
|
||||||
SOURCE = ApiSource("https://matchtv.ru")
|
SOURCE = ApiSource("https://matchtv.ru")
|
||||||
|
|
||||||
async def get_channels(self) -> list[ChannelId]:
|
async def find_channels(self, query: str) -> list[Channel]:
|
||||||
return [
|
endpoint = "api/v1/channels"
|
||||||
ChannelId.MATCH_TV,
|
data = json.loads(await self.SOURCE.request(endpoint))
|
||||||
ChannelId.MATCH_IGRA,
|
result = []
|
||||||
ChannelId.MATCH_ARENA,
|
query = query.lower()
|
||||||
ChannelId.MATCH_FUTBOL_1,
|
for item in data["result"]:
|
||||||
ChannelId.MATCH_FUTBOL_2,
|
if query in item["name"].lower() or query in item["alias"]:
|
||||||
ChannelId.MATCH_FUTBOL_3,
|
channel_id = item["alias"]
|
||||||
ChannelId.MATCH_STRANA,
|
name = item["name"].split("|")[0].strip()
|
||||||
]
|
result.append(Channel(id=channel_id, name=name, provider=self.provider))
|
||||||
|
return result
|
||||||
|
|
||||||
async def get_channel_schedule(
|
async def get_schedule(self, channel_id: str, date: datetime.date) -> Schedule:
|
||||||
self, channel_id: ChannelId, date: datetime.date
|
endpoint = f"api/v1/channels/{channel_id}/tv-schedule?date={date:%Y%m%d}"
|
||||||
) -> Schedule:
|
data = json.loads(await self.SOURCE.request(endpoint))
|
||||||
endpoint = f"tvguide/{channel_id}?date={date:%Y%m%d}"
|
channel_data = data["result"]["channels"][0]
|
||||||
data = await self.SOURCE.request(endpoint)
|
channel = Channel(id=channel_data["alias"], name=channel_data["name"], provider=self.provider)
|
||||||
soup = BeautifulSoup(data, features="html.parser")
|
|
||||||
values = []
|
values = []
|
||||||
channel_name = (
|
current_day = datetime.datetime.combine(date.today(), datetime.datetime.min.time())
|
||||||
soup.select_one(".p-tv-guide-header__title")
|
|
||||||
.text.replace("Телепрограмма ", "")
|
|
||||||
.strip()
|
|
||||||
)
|
|
||||||
current_day = datetime.datetime.combine(
|
|
||||||
date.today(), datetime.datetime.min.time()
|
|
||||||
)
|
|
||||||
end = current_day + datetime.timedelta(days=1, hours=6)
|
end = current_day + datetime.timedelta(days=1, hours=6)
|
||||||
prev_value: ScheduleValue | None = None
|
prev_value: ScheduleValue | None = None
|
||||||
for item in soup.select(
|
for item in channel_data["schedule"]:
|
||||||
".p-tv-guide-schedule-channel-carcass__transmissions .p-tv-guide-schedule-channel-transmission"
|
title = item["title"]
|
||||||
):
|
time_str = item["time"]
|
||||||
title = item.select_one(
|
|
||||||
".p-tv-guide-schedule-channel-transmission__title"
|
|
||||||
).text.strip()
|
|
||||||
time_str = item.select_one(
|
|
||||||
".p-tv-guide-schedule-channel-transmission__time-block"
|
|
||||||
).text.strip()
|
|
||||||
hours, minutes = map(int, time_str.split(":"))
|
hours, minutes = map(int, time_str.split(":"))
|
||||||
item_date = current_day.replace(hour=hours, minute=minutes)
|
item_date = current_day.replace(hour=hours, minute=minutes)
|
||||||
if prev_value is not None and item_date.hour < prev_value.start.hour:
|
if prev_value is not None and item_date.hour < prev_value.start.hour:
|
||||||
@@ -63,5 +49,7 @@ class MatchTvApi(ScheduleApi):
|
|||||||
prev_value.end = item_date
|
prev_value.end = item_date
|
||||||
prev_value = value
|
prev_value = value
|
||||||
return Schedule(
|
return Schedule(
|
||||||
channel=Channel(id=channel_id, name=channel_name), date=date, values=values
|
channel=channel,
|
||||||
|
date=date,
|
||||||
|
values=values,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from os import environ
|
||||||
|
|
||||||
from aiocache import cached
|
from aiocache import cached
|
||||||
|
|
||||||
@@ -9,7 +10,9 @@ from gallery.sketch.weather.model import Location, WeatherResponse, WeatherValue
|
|||||||
from gallery.sketch.weather.util import merge_weather_values
|
from gallery.sketch.weather.util import merge_weather_values
|
||||||
from gallery.util import TimeUnit
|
from gallery.util import TimeUnit
|
||||||
|
|
||||||
from .openweather import Forecast, OpenWeather
|
from .openweather import Forecast
|
||||||
|
from .openweather import Location as OpenWeatherLocation
|
||||||
|
from .openweather import OpenWeather
|
||||||
from .parser import FORECAST_ITEM_PARSER
|
from .parser import FORECAST_ITEM_PARSER
|
||||||
|
|
||||||
logger = logging.getLogger("openweather")
|
logger = logging.getLogger("openweather")
|
||||||
@@ -17,12 +20,20 @@ logger = logging.getLogger("openweather")
|
|||||||
|
|
||||||
class OpenWeatherApi(WeatherApi):
|
class OpenWeatherApi(WeatherApi):
|
||||||
PROVIDER = "openweather"
|
PROVIDER = "openweather"
|
||||||
SOURCE = OpenWeather("517a6bccceaa1c48127f6199ec3fb7cf")
|
SOURCE = OpenWeather(environ["OPENWEATHER_KEY"])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _parse_location(cls, location_id: str) -> tuple[float, float]:
|
def _parse_location(cls, location_id: str) -> tuple[float, float]:
|
||||||
return tuple(map(float, location_id.split(":", maxsplit=2)))
|
return tuple(map(float, location_id.split(":", maxsplit=2)))
|
||||||
|
|
||||||
|
@cached(
|
||||||
|
key_builder=lambda fun, self, location_id: f"api.weather.{self.provider}.source.{location_id}.location",
|
||||||
|
alias="redis",
|
||||||
|
ttl=TimeUnit.DAY,
|
||||||
|
)
|
||||||
|
async def _get_location(self, location_id: str) -> OpenWeatherLocation:
|
||||||
|
return await self.SOURCE.get_location(*self._parse_location(location_id))
|
||||||
|
|
||||||
@cached(
|
@cached(
|
||||||
key_builder=lambda fun, self, location_id: f"api.weather.{self.provider}.source.{location_id}.forecast",
|
key_builder=lambda fun, self, location_id: f"api.weather.{self.provider}.source.{location_id}.forecast",
|
||||||
alias="redis",
|
alias="redis",
|
||||||
@@ -32,9 +43,24 @@ class OpenWeatherApi(WeatherApi):
|
|||||||
return await self.SOURCE.get_forecast(*self._parse_location(location_id))
|
return await self.SOURCE.get_forecast(*self._parse_location(location_id))
|
||||||
|
|
||||||
async def find_locations(self, query: str) -> list[Location]:
|
async def find_locations(self, query: str) -> list[Location]:
|
||||||
raise NotImplementedError
|
result = await self.SOURCE.find_locations(query)
|
||||||
|
return [
|
||||||
|
Location(
|
||||||
|
id=f"{item.lat}:{item.lon}",
|
||||||
|
name=item.name,
|
||||||
|
provider=self.provider,
|
||||||
|
lat=item.lat,
|
||||||
|
lon=item.lon,
|
||||||
|
country=item.country,
|
||||||
|
country_code=item.country.lower(),
|
||||||
|
district=item.state or "",
|
||||||
|
subdistrict="",
|
||||||
|
)
|
||||||
|
for item in result
|
||||||
|
]
|
||||||
|
|
||||||
async def get_day(self, location_id: str, date: datetime.date) -> WeatherResponse:
|
async def get_day(self, location_id: str, date: datetime.date) -> WeatherResponse:
|
||||||
|
location: OpenWeatherLocation = await self._get_location(location_id)
|
||||||
data: Forecast = await self._get_location_forecast(location_id)
|
data: Forecast = await self._get_location_forecast(location_id)
|
||||||
values = []
|
values = []
|
||||||
for item in data.list:
|
for item in data.list:
|
||||||
@@ -42,25 +68,23 @@ class OpenWeatherApi(WeatherApi):
|
|||||||
if value.date.date() == date:
|
if value.date.date() == date:
|
||||||
values.append(value)
|
values.append(value)
|
||||||
return WeatherResponse(
|
return WeatherResponse(
|
||||||
location=location_id,
|
location=location.name,
|
||||||
date=date,
|
date=date,
|
||||||
period="day",
|
period="day",
|
||||||
values=values,
|
values=values,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_days(self, location_id: str, days: int) -> WeatherResponse:
|
async def get_days(self, location_id: str, days: int) -> WeatherResponse:
|
||||||
|
location: OpenWeatherLocation = await self._get_location(location_id)
|
||||||
data: Forecast = await self._get_location_forecast(location_id)
|
data: Forecast = await self._get_location_forecast(location_id)
|
||||||
values_by_date: dict[datetime.datetime, list[WeatherValue]] = defaultdict(list)
|
values_by_date: dict[datetime.datetime, list[WeatherValue]] = defaultdict(list)
|
||||||
for item in data.list:
|
for item in data.list:
|
||||||
value = FORECAST_ITEM_PARSER.parse(item)
|
value = FORECAST_ITEM_PARSER.parse(item)
|
||||||
item_date = value.date.replace(hour=0, minute=0)
|
item_date = value.date.replace(hour=0, minute=0)
|
||||||
values_by_date[item_date].append(value)
|
values_by_date[item_date].append(value)
|
||||||
values = [
|
values = [merge_weather_values(date, values) for date, values in values_by_date.items()]
|
||||||
merge_weather_values(date, values)
|
|
||||||
for date, values in values_by_date.items()
|
|
||||||
]
|
|
||||||
return WeatherResponse(
|
return WeatherResponse(
|
||||||
location=location_id,
|
location=location.name,
|
||||||
date=datetime.date.today(),
|
date=datetime.date.today(),
|
||||||
period="days",
|
period="days",
|
||||||
values=list(sorted(values, key=lambda item: item.date)),
|
values=list(sorted(values, key=lambda item: item.date)),
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from gallery.sketch.mock import MockData
|
|
||||||
|
|
||||||
OPENWEATHER_MOCK_DATA = MockData(Path(__file__).parent / "data")
|
|
||||||
@@ -53,7 +53,7 @@ class ForecastItem(Model):
|
|||||||
weather: list[Weather]
|
weather: list[Weather]
|
||||||
clouds: Clouds
|
clouds: Clouds
|
||||||
wind: Wind
|
wind: Wind
|
||||||
visibility: int
|
visibility: int | None = None
|
||||||
pop: float
|
pop: float
|
||||||
rain: Rain | None = None
|
rain: Rain | None = None
|
||||||
sys: Sys
|
sys: Sys
|
||||||
@@ -67,6 +67,15 @@ class Forecast(Model):
|
|||||||
list: list[ForecastItem]
|
list: list[ForecastItem]
|
||||||
|
|
||||||
|
|
||||||
|
class Location(Model):
|
||||||
|
name: str
|
||||||
|
lat: float
|
||||||
|
lon: float
|
||||||
|
country: str
|
||||||
|
state: str | None = None
|
||||||
|
local_names: dict[str, str] | None = None
|
||||||
|
|
||||||
|
|
||||||
class OpenWeather:
|
class OpenWeather:
|
||||||
BASE_URL = "https://api.openweathermap.org"
|
BASE_URL = "https://api.openweathermap.org"
|
||||||
|
|
||||||
@@ -75,9 +84,20 @@ class OpenWeather:
|
|||||||
self._source = ApiSource(self.BASE_URL)
|
self._source = ApiSource(self.BASE_URL)
|
||||||
|
|
||||||
async def get_forecast(self, lat: float, lon: float) -> Forecast:
|
async def get_forecast(self, lat: float, lon: float) -> Forecast:
|
||||||
endpoint = (
|
endpoint = f"data/2.5/forecast?lat={lat}&lon={lon}&appid={self._api_key}&units=metric"
|
||||||
f"data/2.5/forecast?lat={lat}&lon={lon}&appid={self._api_key}&units=metric"
|
|
||||||
)
|
|
||||||
response = await self._source.request(endpoint)
|
response = await self._source.request(endpoint)
|
||||||
response_data = json.loads(response)
|
response_data = json.loads(response)
|
||||||
return Forecast(**response_data)
|
return Forecast.model_validate(response_data)
|
||||||
|
|
||||||
|
async def find_locations(self, query: str, limit: int = 5) -> list[Location]:
|
||||||
|
endpoint = f"geo/1.0/direct?q={query}&limit={limit}&appid={self._api_key}"
|
||||||
|
response = await self._source.request(endpoint)
|
||||||
|
response_data = json.loads(response)
|
||||||
|
return [Location.model_validate(item) for item in response_data]
|
||||||
|
|
||||||
|
async def get_location(self, lat: float, lon: float) -> Location:
|
||||||
|
limit = 1
|
||||||
|
endpoint = f"geo/1.0/reverse?lat={lat}&lon={lon}&limit={limit}&appid={self._api_key}"
|
||||||
|
response = await self._source.request(endpoint)
|
||||||
|
response_data = json.loads(response)
|
||||||
|
return Location.model_validate(response_data[0])
|
||||||
|
|||||||
@@ -24,11 +24,7 @@ class ForecastItemParser:
|
|||||||
|
|
||||||
def parse(self, item: ForecastItem) -> WeatherValue:
|
def parse(self, item: ForecastItem) -> WeatherValue:
|
||||||
item_date = datetime.datetime.fromtimestamp(item.dt, datetime.UTC)
|
item_date = datetime.datetime.fromtimestamp(item.dt, datetime.UTC)
|
||||||
item_date = (
|
item_date = item_date.replace(tzinfo=datetime.timezone.utc).astimezone(tz=None).replace(tzinfo=None)
|
||||||
item_date.replace(tzinfo=datetime.timezone.utc)
|
|
||||||
.astimezone(tz=None)
|
|
||||||
.replace(tzinfo=None)
|
|
||||||
)
|
|
||||||
value = build_weather_value(item_date)
|
value = build_weather_value(item_date)
|
||||||
# TODO parse temperature interval flag
|
# TODO parse temperature interval flag
|
||||||
value.temperature = [round(item.main.temp)]
|
value.temperature = [round(item.main.temp)]
|
||||||
@@ -38,12 +34,8 @@ class ForecastItemParser:
|
|||||||
value.wind_speed = round(item.wind.speed)
|
value.wind_speed = round(item.wind.speed)
|
||||||
value.wind_gust = round(item.wind.gust)
|
value.wind_gust = round(item.wind.gust)
|
||||||
value.wind_direction = item.wind.deg
|
value.wind_direction = item.wind.deg
|
||||||
value.sky.cloudness = self.CLOUDNESS_MAP.get(
|
value.sky.cloudness = self.CLOUDNESS_MAP.get(item.weather[0].description, Cloudness.CLEAR)
|
||||||
item.weather[0].description, Cloudness.CLEAR
|
value.sky.precipitation = self.PRECIPITATION_MAP.get(item.weather[0].description, Precipitation.NO)
|
||||||
)
|
|
||||||
value.sky.precipitation = self.PRECIPITATION_MAP.get(
|
|
||||||
item.weather[0].description, Precipitation.NO
|
|
||||||
)
|
|
||||||
if item.rain:
|
if item.rain:
|
||||||
value.precipitation = round(item.rain.interval_3h, 1)
|
value.precipitation = round(item.rain.interval_3h, 1)
|
||||||
return value
|
return value
|
||||||
|
|||||||
@@ -1,27 +1,15 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from gallery.sketch.schedule.api import ScheduleApi
|
from gallery.sketch.schedule.api import ScheduleApi
|
||||||
from gallery.sketch.schedule.model import Channel, ChannelId, Schedule, ScheduleValue
|
from gallery.sketch.schedule.model import Channel, Schedule, ScheduleValue
|
||||||
from gallery.sketch.source import ApiSource
|
from gallery.sketch.source import ApiSource
|
||||||
|
|
||||||
logger = logging.getLogger("matchtv")
|
logger = logging.getLogger("matchtv")
|
||||||
|
|
||||||
CHANNELS_MAP: dict[ChannelId, str] = {
|
|
||||||
ChannelId.MATCH_TV: "match-tv-49",
|
|
||||||
ChannelId.MATCH_IGRA: "match-igra-1174",
|
|
||||||
ChannelId.MATCH_ARENA: "match-arena-1173",
|
|
||||||
ChannelId.MATCH_FUTBOL_1: "match-futbol-1-646",
|
|
||||||
ChannelId.MATCH_FUTBOL_2: "match-futbol-2-593",
|
|
||||||
ChannelId.MATCH_FUTBOL_3: "match-futbol-3-797",
|
|
||||||
ChannelId.MATCH_STRANA: "match-strana-1356",
|
|
||||||
ChannelId.MATCH_PLANETA: "match-planeta-1177",
|
|
||||||
# ChannelId.EUROSPORT: "eurosport-677",
|
|
||||||
# ChannelId.EUROSPORT_2: "eurosport-2-720",
|
|
||||||
ChannelId.START: "start-103",
|
|
||||||
}
|
|
||||||
|
|
||||||
HEADERS: dict[str, str] = {
|
HEADERS: dict[str, str] = {
|
||||||
"Accept": (
|
"Accept": (
|
||||||
@@ -57,22 +45,28 @@ class YandexTvApi(ScheduleApi):
|
|||||||
PROVIDER = "yandextv"
|
PROVIDER = "yandextv"
|
||||||
SOURCE = ApiSource("https://tv.yandex.ru", headers=HEADERS)
|
SOURCE = ApiSource("https://tv.yandex.ru", headers=HEADERS)
|
||||||
|
|
||||||
async def get_channels(self) -> list[ChannelId]:
|
async def find_channels(self, query: str) -> list[Channel]:
|
||||||
return list(CHANNELS_MAP.keys())
|
url = (
|
||||||
|
"https://suggest-multi.yandex.ru/suggest-tv2?"
|
||||||
|
f"v=4&uil=ru&lr=10&count_channels=4&count_programs=4&sn=50&part={query}"
|
||||||
|
)
|
||||||
|
_, values = json.loads(await self.SOURCE.request(url))
|
||||||
|
result = []
|
||||||
|
for _, name, content in values:
|
||||||
|
if content["label"] == "Каналы":
|
||||||
|
channel_id = content["url"].split("/")[-1]
|
||||||
|
result.append(Channel(id=channel_id, name=name, provider=self.provider))
|
||||||
|
return result
|
||||||
|
|
||||||
async def get_channel_schedule(
|
async def get_schedule(self, channel_id: str, date: datetime.date) -> Schedule:
|
||||||
self, channel_id: ChannelId, date: datetime.date
|
endpoint = f"channels/{channel_id}?date={date:%Y-%m-%d}"
|
||||||
) -> Schedule:
|
|
||||||
endpoint = f"channel/{CHANNELS_MAP[channel_id]}?date={date:%Y-%m-%d}"
|
|
||||||
data = await self.SOURCE.request(endpoint)
|
data = await self.SOURCE.request(endpoint)
|
||||||
soup = BeautifulSoup(data, features="html.parser")
|
soup = BeautifulSoup(data, features="html.parser")
|
||||||
if soup.select_one(".CheckboxCaptcha") is not None:
|
if soup.select_one(".CheckboxCaptcha") is not None:
|
||||||
raise RuntimeError("Captcha")
|
raise RuntimeError("Captcha")
|
||||||
values = []
|
values = []
|
||||||
channel_name = soup.select_one(".channel-header__text").text.strip()
|
channel_name = soup.select_one(".channel-header__text").text.strip()
|
||||||
current_day = datetime.datetime.combine(
|
current_day = datetime.datetime.combine(date.today(), datetime.datetime.min.time())
|
||||||
date.today(), datetime.datetime.min.time()
|
|
||||||
)
|
|
||||||
end = current_day + datetime.timedelta(days=1, hours=6)
|
end = current_day + datetime.timedelta(days=1, hours=6)
|
||||||
prev_value: ScheduleValue | None = None
|
prev_value: ScheduleValue | None = None
|
||||||
for item in soup.select(".channel-schedule .channel-schedule__event"):
|
for item in soup.select(".channel-schedule .channel-schedule__event"):
|
||||||
@@ -90,5 +84,7 @@ class YandexTvApi(ScheduleApi):
|
|||||||
prev_value.end = item_date
|
prev_value.end = item_date
|
||||||
prev_value = value
|
prev_value = value
|
||||||
return Schedule(
|
return Schedule(
|
||||||
channel=Channel(id=channel_id, name=channel_name), date=date, values=values
|
channel=Channel(id=channel_id, name=channel_name, provider=self.provider),
|
||||||
|
date=date,
|
||||||
|
values=values,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,8 +2,13 @@ from typing import TypeVar
|
|||||||
|
|
||||||
|
|
||||||
class Api:
|
class Api:
|
||||||
|
TYPE: str
|
||||||
PROVIDER: str
|
PROVIDER: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self) -> str:
|
||||||
|
return self.TYPE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def provider(self) -> str:
|
def provider(self) -> str:
|
||||||
return self.PROVIDER
|
return self.PROVIDER
|
||||||
|
|||||||
@@ -1,28 +1,19 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from .api import API, Api
|
from .api import API, Api
|
||||||
from .schedule.api import ScheduleApi
|
|
||||||
from .weather.api import WeatherApi
|
|
||||||
|
|
||||||
|
|
||||||
class ApiBundle(list[Api]):
|
class ApiBundle:
|
||||||
def __init__(self, values: list[Api]) -> None:
|
def __init__(self, values: list[Api]):
|
||||||
super().__init__(values)
|
self._values = values
|
||||||
|
self._providers_by_api = defaultdict(list)
|
||||||
|
self._api_map = {}
|
||||||
|
for value in self._values:
|
||||||
|
self._providers_by_api[value.type].append(value.provider)
|
||||||
|
self._api_map[(value.type, value.provider)] = value
|
||||||
|
|
||||||
def get_api_by_provider(self, provider: str) -> Api:
|
def get_api_providers(self, api_type: type[API]) -> list[str]:
|
||||||
for value in self:
|
return self._providers_by_api[api_type.TYPE]
|
||||||
if value.PROVIDER == provider:
|
|
||||||
return value
|
|
||||||
raise ValueError(provider)
|
|
||||||
|
|
||||||
def get_api_by_type(self, api_type: type[API]) -> API:
|
def get_api(self, api_type: type[API], provider: str) -> API:
|
||||||
for value in self:
|
return self._api_map[(api_type.TYPE, provider)]
|
||||||
if isinstance(value, api_type):
|
|
||||||
return value
|
|
||||||
raise ValueError(api_type)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def weather(self) -> WeatherApi:
|
|
||||||
return self.get_api_by_type(WeatherApi)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def schedule(self) -> ScheduleApi:
|
|
||||||
return self.get_api_by_type(ScheduleApi)
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
from typing import Generic, TypeVar
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
|
|
||||||
|
|
||||||
class CatalogBundle(Generic[T]):
|
|
||||||
def __init__(self, items: list[T]) -> None:
|
|
||||||
self._items_by_id = {item.id: item for item in items}
|
|
||||||
|
|
||||||
def get_item(self, item_id: str) -> T:
|
|
||||||
return self._items_by_id[item_id]
|
|
||||||
|
|
||||||
def select_items(self, ids: list[str]) -> list[T]:
|
|
||||||
return [self._items_by_id[id_] for id_ in ids]
|
|
||||||
@@ -1,28 +1,14 @@
|
|||||||
import asyncio
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from ..api import Api
|
from ..api import Api
|
||||||
from .model import ChannelId, Schedule
|
from .model import Channel, Schedule
|
||||||
|
|
||||||
|
|
||||||
class ScheduleApi(Api):
|
class ScheduleApi(Api):
|
||||||
INTERVAL: float = 0.5
|
TYPE = "schedule"
|
||||||
|
|
||||||
async def get_channels(self) -> list[ChannelId]:
|
async def find_channels(self, query: str) -> list[Channel]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def get_channel_schedule(
|
async def get_schedule(self, channel_id: str, date: datetime.date) -> Schedule:
|
||||||
self, channel_id: ChannelId, date: datetime.date
|
|
||||||
) -> Schedule:
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def get_all_schedules(self, date: datetime.date) -> list[Schedule]:
|
|
||||||
channels = await self.get_channels()
|
|
||||||
results = []
|
|
||||||
for channel in channels:
|
|
||||||
results.append(
|
|
||||||
await self.get_channel_schedule(channel_id=channel, date=date)
|
|
||||||
)
|
|
||||||
if self.INTERVAL > 0:
|
|
||||||
await asyncio.sleep(self.INTERVAL)
|
|
||||||
return results
|
|
||||||
|
|||||||
@@ -6,20 +6,20 @@ from gallery.sketch.cached import CachedApi, CachePreset
|
|||||||
from gallery.util import TimeUnit
|
from gallery.util import TimeUnit
|
||||||
|
|
||||||
from .api import ScheduleApi
|
from .api import ScheduleApi
|
||||||
from .model import ChannelId, Schedule
|
from .model import Channel, Schedule
|
||||||
|
|
||||||
CACHE_PRESET = CachePreset(ttl=TimeUnit.HOUR * 6)
|
CACHE_PRESET = CachePreset(ttl=TimeUnit.HOUR * 6)
|
||||||
|
|
||||||
|
|
||||||
class CachedScheduleApi(ScheduleApi, CachedApi[ScheduleApi]):
|
class CachedScheduleApi(ScheduleApi, CachedApi[ScheduleApi]):
|
||||||
CACHE_KEY = "schedule"
|
CACHE_KEY = ScheduleApi.TYPE
|
||||||
|
|
||||||
@cached(
|
@cached(
|
||||||
key_builder=lambda fun, self: f"api.{self.CACHE_KEY}.{self.provider}.channels",
|
key_builder=lambda fun, self, query: f"api.{self.CACHE_KEY}.{self.provider}.find.{query}",
|
||||||
**CACHE_PRESET._asdict(),
|
**CACHE_PRESET._asdict(),
|
||||||
)
|
)
|
||||||
async def get_channels(self) -> list[ChannelId]:
|
async def find_channels(self, query: str) -> list[Channel]:
|
||||||
return await self._api.get_channels()
|
return await self._api.find_channels(query)
|
||||||
|
|
||||||
@cached(
|
@cached(
|
||||||
key_builder=lambda fun, self, channel_id, date: (
|
key_builder=lambda fun, self, channel_id, date: (
|
||||||
@@ -27,16 +27,5 @@ class CachedScheduleApi(ScheduleApi, CachedApi[ScheduleApi]):
|
|||||||
),
|
),
|
||||||
**CACHE_PRESET._asdict(),
|
**CACHE_PRESET._asdict(),
|
||||||
)
|
)
|
||||||
async def get_channel_schedule(
|
async def get_schedule(self, channel_id: str, date: datetime.date) -> Schedule:
|
||||||
self, channel_id: ChannelId, date: datetime.date
|
return await self._api.get_schedule(channel_id, date)
|
||||||
) -> Schedule:
|
|
||||||
return await self._api.get_channel_schedule(channel_id, date)
|
|
||||||
|
|
||||||
@cached(
|
|
||||||
key_builder=lambda fun, self, date: (
|
|
||||||
f"api.{self.CACHE_KEY}.{self.provider}.all.{date}"
|
|
||||||
),
|
|
||||||
**CACHE_PRESET._asdict(),
|
|
||||||
)
|
|
||||||
async def get_all_schedules(self, date: datetime.date) -> list[Schedule]:
|
|
||||||
return await self._api.get_all_schedules(date)
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
from gallery.sketch.catalog import CatalogBundle
|
|
||||||
|
|
||||||
from .model import Channel, ChannelId
|
|
||||||
|
|
||||||
BUNDLE = CatalogBundle(
|
|
||||||
[
|
|
||||||
Channel(id=ChannelId.MATCH_TV, name="Матч ТВ"),
|
|
||||||
Channel(id=ChannelId.MATCH_IGRA, name="Матч! Игра"),
|
|
||||||
Channel(id=ChannelId.MATCH_ARENA, name="Матч! Арена"),
|
|
||||||
Channel(id=ChannelId.MATCH_FUTBOL_1, name="Футбол 1"),
|
|
||||||
Channel(id=ChannelId.MATCH_FUTBOL_2, name="Футбол 2"),
|
|
||||||
Channel(id=ChannelId.MATCH_FUTBOL_3, name="Футбол 3"),
|
|
||||||
Channel(id=ChannelId.MATCH_STRANA, name="Матч! Страна"),
|
|
||||||
Channel(id=ChannelId.MATCH_PLANETA, name="Матч! Планета"),
|
|
||||||
Channel(id=ChannelId.MATCH_PLANETA, name="Матч! Планета"),
|
|
||||||
Channel(id=ChannelId.EUROSPORT, name="Europsort"),
|
|
||||||
Channel(id=ChannelId.EUROSPORT_2, name="Europsort 2"),
|
|
||||||
Channel(id=ChannelId.START, name="Старт!"),
|
|
||||||
Channel(id=ChannelId.TEST, name="Тест"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from enum import StrEnum
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
@@ -9,27 +8,10 @@ class Model(BaseModel):
|
|||||||
use_enum_values = True
|
use_enum_values = True
|
||||||
|
|
||||||
|
|
||||||
class ChannelId(StrEnum):
|
|
||||||
MATCH_TV = "matchtv"
|
|
||||||
MATCH_IGRA = "igra"
|
|
||||||
MATCH_ARENA = "arena"
|
|
||||||
MATCH_FUTBOL_1 = "futbol-1"
|
|
||||||
MATCH_FUTBOL_2 = "futbol-2"
|
|
||||||
MATCH_FUTBOL_3 = "futbol-3"
|
|
||||||
MATCH_STRANA = "strana"
|
|
||||||
MATCH_PLANETA = "planeta"
|
|
||||||
EUROSPORT = "eurosport"
|
|
||||||
EUROSPORT_2 = "eurosport-2"
|
|
||||||
START = "start"
|
|
||||||
TEST = "test"
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
|
|
||||||
class Channel(Model):
|
class Channel(Model):
|
||||||
id: ChannelId
|
id: str
|
||||||
name: str
|
name: str
|
||||||
|
provider: str
|
||||||
|
|
||||||
|
|
||||||
class ScheduleValue(Model):
|
class ScheduleValue(Model):
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ logger = logging.getLogger("source")
|
|||||||
|
|
||||||
class ApiSource:
|
class ApiSource:
|
||||||
DEFAULT_USER_AGENT = (
|
DEFAULT_USER_AGENT = (
|
||||||
"Mozilla/5.0 (X11; Linux x86_64) "
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
|
||||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
|
||||||
"Chrome/126.0.0.0 Safari/537.36"
|
|
||||||
)
|
)
|
||||||
DEFAULT_TIMEOUT = 30.0
|
DEFAULT_TIMEOUT = 30.0
|
||||||
|
|
||||||
@@ -28,7 +26,10 @@ class ApiSource:
|
|||||||
self._headers = headers
|
self._headers = headers
|
||||||
|
|
||||||
async def request(self, endpoint: str) -> str:
|
async def request(self, endpoint: str) -> str:
|
||||||
url = f"{self._base_url}/{endpoint}"
|
if endpoint.startswith("https:"):
|
||||||
|
url = endpoint
|
||||||
|
else:
|
||||||
|
url = f"{self._base_url}/{endpoint}"
|
||||||
logger.info(url)
|
logger.info(url)
|
||||||
headers = {"User-Agent": self._user_agent, **(self._headers or {})}
|
headers = {"User-Agent": self._user_agent, **(self._headers or {})}
|
||||||
async with aiohttp.ClientSession(
|
async with aiohttp.ClientSession(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from .model import Location, WeatherResponse
|
|||||||
|
|
||||||
|
|
||||||
class WeatherApi(Api):
|
class WeatherApi(Api):
|
||||||
|
TYPE = "weather"
|
||||||
|
|
||||||
async def find_locations(self, query: str) -> list[Location]:
|
async def find_locations(self, query: str) -> list[Location]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ CACHE_PRESET = DEFAULT_CACHE_PRESET
|
|||||||
|
|
||||||
|
|
||||||
class CachedWeatherApi(WeatherApi, CachedApi[WeatherApi]):
|
class CachedWeatherApi(WeatherApi, CachedApi[WeatherApi]):
|
||||||
CACHE_KEY = "weather"
|
CACHE_KEY = WeatherApi.TYPE
|
||||||
|
|
||||||
@cached(
|
@cached(
|
||||||
key_builder=lambda fun, self, query: f"api.{self.CACHE_KEY}.{self.provider}.locations.{query}",
|
key_builder=lambda fun, self, query: f"api.{self.CACHE_KEY}.{self.provider}.locations.{query}",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from enum import Enum
|
from enum import StrEnum, auto
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
@@ -12,28 +13,31 @@ class Model(BaseModel):
|
|||||||
class Location(Model):
|
class Location(Model):
|
||||||
id: str
|
id: str
|
||||||
name: str
|
name: str
|
||||||
|
provider: str
|
||||||
lat: float
|
lat: float
|
||||||
lon: float
|
lon: float
|
||||||
country: str
|
country: str
|
||||||
|
country_code: str
|
||||||
district: str
|
district: str
|
||||||
subdistrict: str
|
subdistrict: str
|
||||||
|
|
||||||
|
|
||||||
class Cloudness(str, Enum):
|
class Cloudness(StrEnum):
|
||||||
CLEAR = "clear"
|
CLEAR = auto()
|
||||||
PARTLY_CLOUDY = "party_cloudy"
|
PARTLY_CLOUDY = auto()
|
||||||
CLOUDY = "cloudy"
|
CLOUDY = auto()
|
||||||
MAINLY_CLOUDY = "mainly_cloudy"
|
MAINLY_CLOUDY = auto()
|
||||||
|
|
||||||
|
|
||||||
class Precipitation(str, Enum):
|
class Precipitation(StrEnum):
|
||||||
NO = "no"
|
NO = auto()
|
||||||
SMALL_RAIN = "small_rain"
|
SMALL_RAIN = auto()
|
||||||
RAIN = "rain"
|
RAIN = auto()
|
||||||
HEAVY_RAIN = "heavy_rain"
|
HEAVY_RAIN = auto()
|
||||||
SHOWER = "shower"
|
SHOWER = auto()
|
||||||
SNOW = "snow"
|
SNOW = auto()
|
||||||
HEAVY_SNOW = "heavy_snow"
|
HEAVY_SNOW = auto()
|
||||||
|
HAIL = auto()
|
||||||
|
|
||||||
|
|
||||||
class Sky(Model):
|
class Sky(Model):
|
||||||
@@ -43,16 +47,16 @@ class Sky(Model):
|
|||||||
fog: bool
|
fog: bool
|
||||||
|
|
||||||
|
|
||||||
class WindDirection(str, Enum):
|
class WindDirection(StrEnum):
|
||||||
CALM = "calm"
|
CALM = auto()
|
||||||
N = "N"
|
N = auto()
|
||||||
NE = "NE"
|
NE = auto()
|
||||||
E = "E"
|
E = auto()
|
||||||
SE = "SE"
|
SE = auto()
|
||||||
S = "S"
|
S = auto()
|
||||||
SW = "SW"
|
SW = auto()
|
||||||
W = "W"
|
W = auto()
|
||||||
NW = "NW"
|
NW = auto()
|
||||||
|
|
||||||
|
|
||||||
class WindDirectionDeg(float):
|
class WindDirectionDeg(float):
|
||||||
@@ -66,7 +70,9 @@ class WindDirectionDeg(float):
|
|||||||
|
|
||||||
# pylint:disable=too-many-return-statements
|
# pylint:disable=too-many-return-statements
|
||||||
def to_direction(self) -> WindDirection:
|
def to_direction(self) -> WindDirection:
|
||||||
if self > 337.5 or self <= 22.25:
|
if self == -1:
|
||||||
|
return WindDirection.CALM
|
||||||
|
elif self > 337.5 or self <= 22.25:
|
||||||
return WindDirection.N
|
return WindDirection.N
|
||||||
elif self <= 67.5:
|
elif self <= 67.5:
|
||||||
return WindDirection.NE
|
return WindDirection.NE
|
||||||
@@ -83,10 +89,10 @@ class WindDirectionDeg(float):
|
|||||||
elif self <= 337.5:
|
elif self <= 337.5:
|
||||||
return WindDirection.NW
|
return WindDirection.NW
|
||||||
else:
|
else:
|
||||||
return WindDirection.CALM
|
raise ValueError(self)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_direction(cls, direction: WindDirection) -> "WindDirectionDeg":
|
def from_direction(cls, direction: WindDirection) -> Self:
|
||||||
return cls(
|
return cls(
|
||||||
{
|
{
|
||||||
WindDirection.CALM: -1,
|
WindDirection.CALM: -1,
|
||||||
|
|||||||
@@ -23,9 +23,7 @@ def build_weather_value(date: datetime.datetime) -> WeatherValue:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def merge_weather_values(
|
def merge_weather_values(date: datetime.datetime, values: list[WeatherValue]) -> WeatherValue:
|
||||||
date: datetime.datetime, values: list[WeatherValue]
|
|
||||||
) -> WeatherValue:
|
|
||||||
result = build_weather_value(date)
|
result = build_weather_value(date)
|
||||||
temperatures = []
|
temperatures = []
|
||||||
pressures = []
|
pressures = []
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
__version__ = "0.2.0"
|
import tomllib
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
__version__ = tomllib.loads((Path(__file__).parent.parent / "pyproject.toml").read_text())["tool"]["poetry"]["version"]
|
||||||
|
|||||||
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 "Прямые трансляции"
|
||||||
3417
poetry.lock
generated
@@ -1,31 +1,32 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "gallery"
|
name = "gallery"
|
||||||
version = "0.2.0"
|
version = "0.4.2"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["shmyga <shmyga.z@gmail.com>"]
|
authors = ["shmyga <shmyga.z@gmail.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
packages = [{ include = "gallery" }]
|
packages = [{ include = "gallery" }]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.12"
|
python = "^3.14"
|
||||||
aiohttp = "^3.9.5"
|
aiohttp = "^3.14"
|
||||||
beautifulsoup4 = "^4.12.3"
|
beautifulsoup4 = "^4.15"
|
||||||
dateparser = "^1.2.0"
|
dateparser = "^1.4"
|
||||||
pydantic = "^2.8.2"
|
pydantic = "^2.13"
|
||||||
aiocache = {extras = ["redis"], version = "^0.12.2"}
|
aiocache = { extras = ["redis"], version = "^0.12" }
|
||||||
|
|
||||||
[tool.poetry.group.app.dependencies]
|
[tool.poetry.group.app.dependencies]
|
||||||
fastapi = "^0.111.1"
|
fastapi = {extras = ["standard"], version = "^0.139.0"}
|
||||||
jinja2 = "^3.1.4"
|
jinja2 = "^3.1"
|
||||||
|
babel = "^2.18"
|
||||||
|
|
||||||
[tool.poetry.group.test.dependencies]
|
[tool.poetry.group.test.dependencies]
|
||||||
pytest = "^8.3.1"
|
pytest = "^9.1"
|
||||||
pytest-asyncio = "^0.23.8"
|
pytest-asyncio = "^1.4"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
pylint = "^3.2.6"
|
pylint = "^4.0"
|
||||||
black = "^24.4.2"
|
black = "^26.5"
|
||||||
isort = "^5.13.2"
|
isort = "^8.0"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
@@ -34,9 +35,13 @@ build-backend = "poetry.core.masonry.api"
|
|||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
gallery = "gallery.main:run"
|
gallery = "gallery.main:run"
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 120
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
profile = "black"
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
addopts = "-p no:warnings"
|
addopts = "-p no:warnings"
|
||||||
asyncio_mode = "auto"
|
asyncio_mode = "auto"
|
||||||
testpaths = ["tests"]
|
testpaths = ["tests"]
|
||||||
|
|
||||||
[tool.poetry_bumpversion.file."gallery/version.py"]
|
|
||||||
8
scripts/format
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
cd "$(dirname $(dirname "$0"))" || exit
|
||||||
|
|
||||||
|
TARGET="gallery"
|
||||||
|
|
||||||
|
poetry run isort $TARGET
|
||||||
|
poetry run black $TARGET -q
|
||||||
@@ -2,4 +2,8 @@
|
|||||||
set -e
|
set -e
|
||||||
cd "$(dirname $(dirname "$0"))" || exit
|
cd "$(dirname $(dirname "$0"))" || exit
|
||||||
|
|
||||||
poetry run pylint gallery
|
TARGET="gallery"
|
||||||
|
|
||||||
|
poetry run pylint $TARGET -sn
|
||||||
|
poetry run isort $TARGET --check-only
|
||||||
|
poetry run black $TARGET -q --check --diff
|
||||||
|
|||||||
@@ -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
|
||||||
22
scripts/setup
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
cd "$(dirname $(dirname "$0"))" || exit
|
||||||
|
|
||||||
|
# env
|
||||||
|
if [[ ! -f .env ]]; then
|
||||||
|
cp .env-base .env
|
||||||
|
fi
|
||||||
|
source .env
|
||||||
|
|
||||||
|
# python
|
||||||
|
poetry env use ${PYTHON_VERSION}
|
||||||
|
poetry install
|
||||||
|
|
||||||
|
# static
|
||||||
|
cd static || exit
|
||||||
|
|
||||||
|
if [[ -f $HOME/.nvm/nvm.sh ]]; then
|
||||||
|
source "$HOME/.nvm/nvm.sh"
|
||||||
|
nvm use
|
||||||
|
fi
|
||||||
|
npm ci
|
||||||
@@ -2,4 +2,4 @@
|
|||||||
set -e
|
set -e
|
||||||
cd "$(dirname $(dirname "$0"))" || exit
|
cd "$(dirname $(dirname "$0"))" || exit
|
||||||
|
|
||||||
poetry run pytest tests
|
OPENWEATHER_KEY="" poetry run pytest tests
|
||||||
|
|||||||
15
static/package-lock.json
generated
@@ -1,17 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "gallery-static",
|
"name": "gallery",
|
||||||
"version": "0.2.0",
|
"version": "0.4.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "gallery-static",
|
"name": "gallery",
|
||||||
"version": "0.2.0",
|
"version": "0.4.2",
|
||||||
"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.0",
|
"version": "0.4.2",
|
||||||
"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": {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
class AppLinkElement extends HTMLElement {
|
class AppLinkElement extends HTMLElement {
|
||||||
static observedAttributes = ["icon", "href"];
|
static observedAttributes = ["icon", "href", "target"];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.innerHTML = `
|
this.innerHTML = `
|
||||||
<a href="${this.getAttribute("href")}"
|
<a href="${this.getAttribute("href")}" target="${this.getAttribute("target") || ""}"
|
||||||
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>`;
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import "./main.scss";
|
import * as bootstrap from "bootstrap";
|
||||||
import "bootstrap";
|
|
||||||
import "./theme";
|
|
||||||
import "./language";
|
|
||||||
import "./components";
|
import "./components";
|
||||||
|
import "./language";
|
||||||
|
import "./main.scss";
|
||||||
|
import "./theme";
|
||||||
|
import "./weather/weather";
|
||||||
|
import "./schedule/schedule";
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", (event) => {
|
||||||
|
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||||
|
const tooltipList = [...tooltipTriggerList].map((tooltipTriggerEl) => new bootstrap.Tooltip(tooltipTriggerEl));
|
||||||
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
48
static/src/lib/bootstrap-icons.scss
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
.icon-link {
|
||||||
|
vertical-align: -0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
|
||||||
|
&.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);
|
||||||
|
}
|
||||||
|
&.bi-arrow-left-square {
|
||||||
|
mask-image: url(bootstrap-icons/icons/arrow-left-square.svg);
|
||||||
|
}
|
||||||
|
&.bi-arrow-right-square {
|
||||||
|
mask-image: url(bootstrap-icons/icons/arrow-right-square.svg);
|
||||||
|
}
|
||||||
|
&.bi-arrow-up-square {
|
||||||
|
mask-image: url(bootstrap-icons/icons/arrow-up-square.svg);
|
||||||
|
}
|
||||||
|
&.bi-file-earmark-text {
|
||||||
|
mask-image: url(bootstrap-icons/icons/file-earmark-text.svg);
|
||||||
|
}
|
||||||
|
}
|
||||||
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
|
||||||
17
static/src/lib/flag-icons.scss
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
@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%;
|
||||||
|
height: 1em;
|
||||||
|
border: 1px solid gray;
|
||||||
|
}
|
||||||
22
static/src/lib/weather-icons/build.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from string import Template
|
||||||
|
|
||||||
|
LIST_ITEM = Template(
|
||||||
|
""".$name {
|
||||||
|
mask-image: url(./svg/$name.svg);
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate():
|
||||||
|
work_dir = Path(__file__).parent
|
||||||
|
data = ""
|
||||||
|
for item in (work_dir / "svg").glob("*.svg"):
|
||||||
|
data += LIST_ITEM.substitute({"name": item.stem})
|
||||||
|
target = work_dir / "classes-list.scss"
|
||||||
|
target.write_text(data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
generate()
|
||||||
93
static/src/lib/weather-icons/classes-list.scss
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
.wi-day-snow {
|
||||||
|
mask-image: url(./svg/wi-day-snow.svg);
|
||||||
|
}
|
||||||
|
.wi-rain-mix {
|
||||||
|
mask-image: url(./svg/wi-rain-mix.svg);
|
||||||
|
}
|
||||||
|
.wi-thunderstorm {
|
||||||
|
mask-image: url(./svg/wi-thunderstorm.svg);
|
||||||
|
}
|
||||||
|
.wi-hail {
|
||||||
|
mask-image: url(./svg/wi-hail.svg);
|
||||||
|
}
|
||||||
|
.wi-day-rain {
|
||||||
|
mask-image: url(./svg/wi-day-rain.svg);
|
||||||
|
}
|
||||||
|
.wi-cloudy {
|
||||||
|
mask-image: url(./svg/wi-cloudy.svg);
|
||||||
|
}
|
||||||
|
.wi-day-hail {
|
||||||
|
mask-image: url(./svg/wi-day-hail.svg);
|
||||||
|
}
|
||||||
|
.wi-night-clear {
|
||||||
|
mask-image: url(./svg/wi-night-clear.svg);
|
||||||
|
}
|
||||||
|
.wi-day-cloudy {
|
||||||
|
mask-image: url(./svg/wi-day-cloudy.svg);
|
||||||
|
}
|
||||||
|
.wi-day-storm-showers {
|
||||||
|
mask-image: url(./svg/wi-day-storm-showers.svg);
|
||||||
|
}
|
||||||
|
.wi-lightning {
|
||||||
|
mask-image: url(./svg/wi-lightning.svg);
|
||||||
|
}
|
||||||
|
.wi-day-rain-mix {
|
||||||
|
mask-image: url(./svg/wi-day-rain-mix.svg);
|
||||||
|
}
|
||||||
|
.wi-day-showers {
|
||||||
|
mask-image: url(./svg/wi-day-showers.svg);
|
||||||
|
}
|
||||||
|
.wi-storm-showers {
|
||||||
|
mask-image: url(./svg/wi-storm-showers.svg);
|
||||||
|
}
|
||||||
|
.wi-snow {
|
||||||
|
mask-image: url(./svg/wi-snow.svg);
|
||||||
|
}
|
||||||
|
.wi-cloud {
|
||||||
|
mask-image: url(./svg/wi-cloud.svg);
|
||||||
|
}
|
||||||
|
.wi-night-alt-snow {
|
||||||
|
mask-image: url(./svg/wi-night-alt-snow.svg);
|
||||||
|
}
|
||||||
|
.wi-night-alt-lightning {
|
||||||
|
mask-image: url(./svg/wi-night-alt-lightning.svg);
|
||||||
|
}
|
||||||
|
.wi-day-sunny {
|
||||||
|
mask-image: url(./svg/wi-day-sunny.svg);
|
||||||
|
}
|
||||||
|
.wi-night-alt-hail {
|
||||||
|
mask-image: url(./svg/wi-night-alt-hail.svg);
|
||||||
|
}
|
||||||
|
.wi-night-alt-cloudy {
|
||||||
|
mask-image: url(./svg/wi-night-alt-cloudy.svg);
|
||||||
|
}
|
||||||
|
.wi-night-alt-storm-showers {
|
||||||
|
mask-image: url(./svg/wi-night-alt-storm-showers.svg);
|
||||||
|
}
|
||||||
|
.wi-day-thunderstorm {
|
||||||
|
mask-image: url(./svg/wi-day-thunderstorm.svg);
|
||||||
|
}
|
||||||
|
.wi-wind-deg {
|
||||||
|
mask-image: url(./svg/wi-wind-deg.svg);
|
||||||
|
}
|
||||||
|
.wi-showers {
|
||||||
|
mask-image: url(./svg/wi-showers.svg);
|
||||||
|
}
|
||||||
|
.wi-night-alt-rain-mix {
|
||||||
|
mask-image: url(./svg/wi-night-alt-rain-mix.svg);
|
||||||
|
}
|
||||||
|
.wi-rain {
|
||||||
|
mask-image: url(./svg/wi-rain.svg);
|
||||||
|
}
|
||||||
|
.wi-night-alt-thunderstorm {
|
||||||
|
mask-image: url(./svg/wi-night-alt-thunderstorm.svg);
|
||||||
|
}
|
||||||
|
.wi-night-alt-showers {
|
||||||
|
mask-image: url(./svg/wi-night-alt-showers.svg);
|
||||||
|
}
|
||||||
|
.wi-day-lightning {
|
||||||
|
mask-image: url(./svg/wi-day-lightning.svg);
|
||||||
|
}
|
||||||
|
.wi-night-alt-rain {
|
||||||
|
mask-image: url(./svg/wi-night-alt-rain.svg);
|
||||||
|
}
|
||||||
107
static/src/lib/weather-icons/classes-wind-aliases.scss
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
@mixin wind-rotate( $val: 0deg ) {
|
||||||
|
-webkit-transform: rotate($val);
|
||||||
|
-moz-transform: rotate($val);
|
||||||
|
-ms-transform: rotate($val);
|
||||||
|
-o-transform: rotate($val);
|
||||||
|
transform: rotate($val);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-wind-calm {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.wi-wind-towards-n {
|
||||||
|
@include wind-rotate(0deg);
|
||||||
|
}
|
||||||
|
.wi-wind-towards-nne {
|
||||||
|
@include wind-rotate(23deg);
|
||||||
|
}
|
||||||
|
.wi-wind-towards-ne {
|
||||||
|
@include wind-rotate(45deg);
|
||||||
|
}
|
||||||
|
.wi-wind-towards-ene {
|
||||||
|
@include wind-rotate(68deg);
|
||||||
|
}
|
||||||
|
.wi-wind-towards-e {
|
||||||
|
@include wind-rotate(90deg);
|
||||||
|
}
|
||||||
|
.wi-wind-towards-ese {
|
||||||
|
@include wind-rotate(113deg);
|
||||||
|
}
|
||||||
|
.wi-wind-towards-se {
|
||||||
|
@include wind-rotate(135deg);
|
||||||
|
}
|
||||||
|
.wi-wind-towards-sse {
|
||||||
|
@include wind-rotate(158deg);
|
||||||
|
}
|
||||||
|
.wi-wind-towards-s {
|
||||||
|
@include wind-rotate(180deg);
|
||||||
|
}
|
||||||
|
.wi-wind-towards-ssw {
|
||||||
|
@include wind-rotate(203deg);
|
||||||
|
}
|
||||||
|
.wi-wind-towards-sw {
|
||||||
|
@include wind-rotate(225deg);
|
||||||
|
}
|
||||||
|
.wi-wind-towards-wsw {
|
||||||
|
@include wind-rotate(248deg);
|
||||||
|
}
|
||||||
|
.wi-wind-towards-w {
|
||||||
|
@include wind-rotate(270deg);
|
||||||
|
}
|
||||||
|
.wi-wind-towards-wnw {
|
||||||
|
@include wind-rotate(293deg);
|
||||||
|
}
|
||||||
|
.wi-wind-towards-nw {
|
||||||
|
@include wind-rotate(313deg);
|
||||||
|
}
|
||||||
|
.wi-wind-towards-nnw {
|
||||||
|
@include wind-rotate(336deg);
|
||||||
|
}
|
||||||
|
.wi-wind-from-n {
|
||||||
|
@include wind-rotate(180deg);
|
||||||
|
}
|
||||||
|
.wi-wind-from-nne {
|
||||||
|
@include wind-rotate(180+23deg);
|
||||||
|
}
|
||||||
|
.wi-wind-from-ne {
|
||||||
|
@include wind-rotate(180+45deg);
|
||||||
|
}
|
||||||
|
.wi-wind-from-ene {
|
||||||
|
@include wind-rotate(180+68deg);
|
||||||
|
}
|
||||||
|
.wi-wind-from-e {
|
||||||
|
@include wind-rotate(180+90deg);
|
||||||
|
}
|
||||||
|
.wi-wind-from-ese {
|
||||||
|
@include wind-rotate(180+113deg);
|
||||||
|
}
|
||||||
|
.wi-wind-from-se {
|
||||||
|
@include wind-rotate(180+135deg);
|
||||||
|
}
|
||||||
|
.wi-wind-from-sse {
|
||||||
|
@include wind-rotate(180+158deg);
|
||||||
|
}
|
||||||
|
.wi-wind-from-s {
|
||||||
|
@include wind-rotate(180+180deg);
|
||||||
|
}
|
||||||
|
.wi-wind-from-ssw {
|
||||||
|
@include wind-rotate(180+203deg);
|
||||||
|
}
|
||||||
|
.wi-wind-from-sw {
|
||||||
|
@include wind-rotate(180+225deg);
|
||||||
|
}
|
||||||
|
.wi-wind-from-wsw {
|
||||||
|
@include wind-rotate(180+248deg);
|
||||||
|
}
|
||||||
|
.wi-wind-from-w {
|
||||||
|
@include wind-rotate(180+270deg);
|
||||||
|
}
|
||||||
|
.wi-wind-from-wnw {
|
||||||
|
@include wind-rotate(180+293deg);
|
||||||
|
}
|
||||||
|
.wi-wind-from-nw {
|
||||||
|
@include wind-rotate(180+313deg);
|
||||||
|
}
|
||||||
|
.wi-wind-from-nnw {
|
||||||
|
@include wind-rotate(180+336deg);
|
||||||
|
}
|
||||||
13
static/src/lib/weather-icons/svg/wi-cloud.svg
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M4.61,16.88c0-1.15,0.36-2.17,1.08-3.07c0.72-0.9,1.63-1.48,2.74-1.73c0.31-1.37,1.02-2.49,2.11-3.37s2.35-1.32,3.76-1.32
|
||||||
|
c1.38,0,2.61,0.43,3.69,1.28s1.78,1.95,2.1,3.29h0.33c0.9,0,1.73,0.22,2.49,0.65s1.37,1.03,1.81,1.79c0.44,0.76,0.67,1.58,0.67,2.48
|
||||||
|
c0,0.88-0.21,1.7-0.63,2.45s-1,1.35-1.73,1.8c-0.73,0.45-1.54,0.69-2.41,0.72H9.41c-1.34-0.06-2.47-0.57-3.4-1.53
|
||||||
|
C5.08,19.37,4.61,18.22,4.61,16.88z M6.32,16.88c0,0.87,0.3,1.62,0.9,2.26s1.33,0.98,2.19,1.03h11.19c0.86-0.04,1.59-0.39,2.19-1.03
|
||||||
|
c0.61-0.64,0.91-1.4,0.91-2.26c0-0.88-0.33-1.63-0.98-2.27c-0.65-0.64-1.42-0.96-2.32-0.96H18.8c-0.11,0-0.17-0.06-0.17-0.18
|
||||||
|
l-0.07-0.57c-0.11-1.08-0.58-1.99-1.4-2.72c-0.82-0.73-1.77-1.1-2.86-1.1c-1.09,0-2.05,0.37-2.85,1.1
|
||||||
|
c-0.81,0.73-1.27,1.64-1.37,2.72l-0.08,0.57c0,0.12-0.07,0.18-0.2,0.18H9.27c-0.84,0.1-1.54,0.46-2.1,1.07S6.32,16.05,6.32,16.88z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
18
static/src/lib/weather-icons/svg/wi-cloudy.svg
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M3.89,17.6c0-0.99,0.31-1.88,0.93-2.65s1.41-1.27,2.38-1.49c0.26-1.17,0.85-2.14,1.78-2.88c0.93-0.75,2-1.12,3.22-1.12
|
||||||
|
c1.18,0,2.24,0.36,3.16,1.09c0.93,0.73,1.53,1.66,1.8,2.8h0.27c1.18,0,2.18,0.41,3.01,1.24s1.25,1.83,1.25,3
|
||||||
|
c0,1.18-0.42,2.18-1.25,3.01s-1.83,1.25-3.01,1.25H8.16c-0.58,0-1.13-0.11-1.65-0.34S5.52,21,5.14,20.62
|
||||||
|
c-0.38-0.38-0.68-0.84-0.91-1.36S3.89,18.17,3.89,17.6z M5.34,17.6c0,0.76,0.28,1.42,0.82,1.96s1.21,0.82,1.99,0.82h9.28
|
||||||
|
c0.77,0,1.44-0.27,1.99-0.82c0.55-0.55,0.83-1.2,0.83-1.96c0-0.76-0.27-1.42-0.83-1.96c-0.55-0.54-1.21-0.82-1.99-0.82h-1.39
|
||||||
|
c-0.1,0-0.15-0.05-0.15-0.15l-0.07-0.49c-0.1-0.94-0.5-1.73-1.19-2.35s-1.51-0.93-2.45-0.93c-0.94,0-1.76,0.31-2.46,0.94
|
||||||
|
c-0.7,0.62-1.09,1.41-1.18,2.34l-0.07,0.42c0,0.1-0.05,0.15-0.16,0.15l-0.45,0.07c-0.72,0.06-1.32,0.36-1.81,0.89
|
||||||
|
C5.59,16.24,5.34,16.87,5.34,17.6z M14.19,8.88c-0.1,0.09-0.08,0.16,0.07,0.21c0.43,0.19,0.79,0.37,1.08,0.55
|
||||||
|
c0.11,0.03,0.19,0.02,0.22-0.03c0.61-0.57,1.31-0.86,2.12-0.86c0.81,0,1.5,0.27,2.1,0.81c0.59,0.54,0.92,1.21,0.99,2l0.09,0.64h1.42
|
||||||
|
c0.65,0,1.21,0.23,1.68,0.7c0.47,0.47,0.7,1.02,0.7,1.66c0,0.6-0.21,1.12-0.62,1.57s-0.92,0.7-1.53,0.77c-0.1,0-0.15,0.05-0.15,0.16
|
||||||
|
v1.13c0,0.11,0.05,0.16,0.15,0.16c1.01-0.06,1.86-0.46,2.55-1.19s1.04-1.6,1.04-2.6c0-1.06-0.37-1.96-1.12-2.7
|
||||||
|
c-0.75-0.75-1.65-1.12-2.7-1.12h-0.15c-0.26-1-0.81-1.82-1.65-2.47c-0.83-0.65-1.77-0.97-2.8-0.97C16.28,7.29,15.11,7.82,14.19,8.88
|
||||||
|
z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
26
static/src/lib/weather-icons/svg/wi-day-cloudy.svg
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M1.56,16.9c0,0.9,0.22,1.73,0.66,2.49s1.04,1.36,1.8,1.8c0.76,0.44,1.58,0.66,2.47,0.66h10.83c0.89,0,1.72-0.22,2.48-0.66
|
||||||
|
c0.76-0.44,1.37-1.04,1.81-1.8c0.44-0.76,0.67-1.59,0.67-2.49c0-0.66-0.14-1.33-0.42-2C22.62,13.98,23,12.87,23,11.6
|
||||||
|
c0-0.71-0.14-1.39-0.41-2.04c-0.27-0.65-0.65-1.2-1.12-1.67C21,7.42,20.45,7.04,19.8,6.77c-0.65-0.28-1.33-0.41-2.04-0.41
|
||||||
|
c-1.48,0-2.77,0.58-3.88,1.74c-0.77-0.44-1.67-0.66-2.7-0.66c-1.41,0-2.65,0.44-3.73,1.31c-1.08,0.87-1.78,1.99-2.08,3.35
|
||||||
|
c-1.12,0.26-2.03,0.83-2.74,1.73S1.56,15.75,1.56,16.9z M3.27,16.9c0-0.84,0.28-1.56,0.84-2.17c0.56-0.61,1.26-0.96,2.1-1.06
|
||||||
|
l0.5-0.03c0.12,0,0.19-0.06,0.19-0.18l0.07-0.54c0.14-1.08,0.61-1.99,1.41-2.71c0.8-0.73,1.74-1.09,2.81-1.09
|
||||||
|
c1.1,0,2.06,0.37,2.87,1.1c0.82,0.73,1.27,1.63,1.37,2.71l0.07,0.58c0.02,0.11,0.09,0.17,0.21,0.17h1.61c0.88,0,1.64,0.32,2.28,0.96
|
||||||
|
c0.64,0.64,0.96,1.39,0.96,2.27c0,0.91-0.32,1.68-0.95,2.32c-0.63,0.64-1.4,0.96-2.28,0.96H6.49c-0.88,0-1.63-0.32-2.27-0.97
|
||||||
|
C3.59,18.57,3.27,17.8,3.27,16.9z M9.97,4.63c0,0.24,0.08,0.45,0.24,0.63l0.66,0.64c0.25,0.19,0.46,0.27,0.64,0.25
|
||||||
|
c0.21,0,0.39-0.09,0.55-0.26s0.24-0.38,0.24-0.62c0-0.24-0.09-0.44-0.26-0.59l-0.59-0.66c-0.18-0.16-0.38-0.24-0.61-0.24
|
||||||
|
c-0.24,0-0.45,0.08-0.62,0.25C10.05,4.19,9.97,4.39,9.97,4.63z M15.31,9.06c0.69-0.67,1.51-1,2.45-1c0.99,0,1.83,0.34,2.52,1.03
|
||||||
|
c0.69,0.69,1.04,1.52,1.04,2.51c0,0.62-0.17,1.24-0.51,1.84C19.84,12.48,18.68,12,17.32,12H17C16.75,10.91,16.19,9.93,15.31,9.06z
|
||||||
|
M16.94,3.78c0,0.26,0.08,0.46,0.23,0.62s0.35,0.23,0.59,0.23c0.26,0,0.46-0.08,0.62-0.23c0.16-0.16,0.23-0.36,0.23-0.62V1.73
|
||||||
|
c0-0.24-0.08-0.43-0.24-0.59s-0.36-0.23-0.61-0.23c-0.24,0-0.43,0.08-0.59,0.23s-0.23,0.35-0.23,0.59V3.78z M22.46,6.07
|
||||||
|
c0,0.26,0.07,0.46,0.22,0.62c0.21,0.16,0.42,0.24,0.62,0.24c0.18,0,0.38-0.08,0.59-0.24l1.43-1.43c0.16-0.18,0.24-0.39,0.24-0.64
|
||||||
|
c0-0.24-0.08-0.44-0.24-0.6c-0.16-0.16-0.36-0.24-0.59-0.24c-0.24,0-0.43,0.08-0.58,0.24l-1.47,1.43
|
||||||
|
C22.53,5.64,22.46,5.84,22.46,6.07z M23.25,17.91c0,0.24,0.08,0.45,0.25,0.63l0.65,0.63c0.15,0.16,0.34,0.24,0.58,0.24
|
||||||
|
s0.44-0.08,0.6-0.25c0.16-0.17,0.24-0.37,0.24-0.62c0-0.22-0.08-0.42-0.24-0.58l-0.65-0.65c-0.16-0.16-0.35-0.24-0.57-0.24
|
||||||
|
c-0.24,0-0.44,0.08-0.6,0.24C23.34,17.47,23.25,17.67,23.25,17.91z M24.72,11.6c0,0.23,0.09,0.42,0.26,0.58
|
||||||
|
c0.16,0.16,0.37,0.24,0.61,0.24h2.04c0.23,0,0.42-0.08,0.58-0.23s0.23-0.35,0.23-0.59c0-0.24-0.08-0.44-0.23-0.6
|
||||||
|
s-0.35-0.25-0.58-0.25h-2.04c-0.24,0-0.44,0.08-0.61,0.25C24.8,11.17,24.72,11.37,24.72,11.6z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.7 KiB |
42
static/src/lib/weather-icons/svg/wi-day-hail.svg
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M1.48,16.88c0,1.34,0.47,2.49,1.4,3.45s2.07,1.47,3.4,1.53c0.12,0,0.18-0.06,0.18-0.17v-1.34c0-0.12-0.06-0.18-0.18-0.18
|
||||||
|
c-0.86-0.04-1.59-0.39-2.19-1.03s-0.9-1.4-0.9-2.26c0-0.83,0.28-1.55,0.85-2.17s1.27-0.97,2.1-1.07l0.53-0.04
|
||||||
|
c0.13,0,0.2-0.06,0.2-0.18l0.07-0.55c0.11-1.08,0.56-1.99,1.37-2.71c0.81-0.73,1.76-1.09,2.86-1.09c1.09,0,2.04,0.36,2.86,1.09
|
||||||
|
c0.82,0.73,1.29,1.63,1.4,2.71l0.07,0.58c0,0.12,0.06,0.19,0.17,0.19h1.62c0.89,0,1.67,0.32,2.32,0.96c0.65,0.64,0.98,1.4,0.98,2.27
|
||||||
|
c0,0.87-0.3,1.62-0.91,2.26c-0.61,0.64-1.34,0.98-2.19,1.03c-0.13,0-0.2,0.06-0.2,0.18v1.34c0,0.11,0.07,0.17,0.2,0.17
|
||||||
|
c1.34-0.04,2.47-0.55,3.39-1.51c0.93-0.97,1.39-2.12,1.39-3.46c0-0.74-0.14-1.41-0.41-2.01c0.79-0.96,1.18-2.07,1.18-3.33
|
||||||
|
c0-0.94-0.24-1.82-0.71-2.63c-0.48-0.81-1.12-1.45-1.93-1.93c-0.81-0.47-1.69-0.71-2.63-0.71c-1.56,0-2.86,0.58-3.9,1.75
|
||||||
|
c-0.8-0.44-1.7-0.66-2.71-0.66c-1.41,0-2.67,0.44-3.76,1.31s-1.8,2-2.11,3.37c-1.11,0.26-2.02,0.84-2.74,1.74
|
||||||
|
C1.84,14.7,1.48,15.73,1.48,16.88z M6.82,23.94c0.1,0.22,0.25,0.37,0.46,0.45c0.2,0.1,0.41,0.11,0.63,0.02
|
||||||
|
c0.22-0.08,0.37-0.23,0.45-0.45c0.1-0.22,0.11-0.43,0.02-0.65c-0.08-0.21-0.23-0.36-0.45-0.44c-0.2-0.11-0.41-0.12-0.62-0.03
|
||||||
|
c-0.22,0.09-0.37,0.24-0.47,0.47C6.74,23.49,6.73,23.7,6.82,23.94z M7.46,21.1c0,0.14,0.03,0.27,0.09,0.38
|
||||||
|
c0.19,0.31,0.49,0.46,0.89,0.46c0.32,0,0.55-0.22,0.69-0.65l1.04-3.22c0.08-0.24,0.06-0.47-0.07-0.67s-0.31-0.33-0.55-0.37
|
||||||
|
C9.34,16.98,9.13,17,8.93,17.1c-0.2,0.11-0.34,0.28-0.41,0.5l-1.03,3.22C7.47,20.92,7.46,21.02,7.46,21.1z M9.33,26.72
|
||||||
|
c0,0.13,0.02,0.23,0.05,0.29c0.09,0.22,0.24,0.37,0.45,0.45c0.09,0.05,0.21,0.07,0.35,0.07c0.06,0,0.16-0.02,0.3-0.06
|
||||||
|
c0.22-0.08,0.38-0.23,0.47-0.45s0.1-0.44,0-0.66c-0.1-0.22-0.25-0.37-0.45-0.45s-0.41-0.08-0.62,0c-0.19,0.07-0.33,0.19-0.42,0.35
|
||||||
|
C9.37,26.42,9.33,26.58,9.33,26.72z M9.94,4.57c0,0.25,0.08,0.45,0.24,0.6l0.65,0.65c0.16,0.16,0.34,0.25,0.54,0.27
|
||||||
|
c0.21,0.03,0.41-0.05,0.61-0.23c0.2-0.18,0.3-0.4,0.3-0.64c0-0.24-0.08-0.44-0.24-0.6l-0.64-0.64c-0.19-0.17-0.39-0.25-0.62-0.25
|
||||||
|
c-0.24,0-0.45,0.08-0.61,0.24C10.02,4.14,9.94,4.34,9.94,4.57z M10.06,24.03c0,0.16,0.05,0.32,0.16,0.48s0.27,0.27,0.48,0.33
|
||||||
|
c0.11,0.02,0.19,0.04,0.24,0.04c0.15,0,0.28-0.03,0.38-0.08c0.2-0.08,0.34-0.27,0.43-0.57l1.8-6.14c0.07-0.24,0.05-0.45-0.06-0.65
|
||||||
|
c-0.11-0.2-0.27-0.33-0.5-0.39c-0.24-0.07-0.46-0.05-0.66,0.06c-0.2,0.11-0.34,0.27-0.41,0.51l-1.84,6.19
|
||||||
|
C10.07,23.92,10.06,24,10.06,24.03z M13.51,23.64c0,0.13,0.02,0.23,0.07,0.31c0.09,0.21,0.24,0.35,0.45,0.44
|
||||||
|
c0.11,0.05,0.22,0.08,0.35,0.08c0.06,0,0.16-0.02,0.3-0.06c0.23-0.09,0.38-0.23,0.46-0.44c0.08-0.22,0.08-0.43,0-0.63
|
||||||
|
c-0.08-0.2-0.22-0.35-0.42-0.45c-0.23-0.11-0.44-0.12-0.66-0.03c-0.21,0.09-0.37,0.24-0.48,0.47
|
||||||
|
C13.53,23.41,13.51,23.51,13.51,23.64z M14.23,21.08c0,0.16,0.05,0.31,0.15,0.45c0.1,0.15,0.26,0.25,0.46,0.31
|
||||||
|
c0.09,0.02,0.17,0.03,0.25,0.03c0.39,0,0.65-0.2,0.79-0.61l1.03-3.18c0.08-0.23,0.05-0.45-0.07-0.65s-0.29-0.33-0.52-0.39
|
||||||
|
c-0.24-0.07-0.45-0.05-0.64,0.06s-0.32,0.27-0.4,0.51l-1.02,3.2C14.25,20.94,14.23,21.03,14.23,21.08z M15.3,9
|
||||||
|
c0.67-0.64,1.5-0.97,2.48-0.97c0.98,0,1.81,0.34,2.5,1.03c0.69,0.68,1.04,1.51,1.04,2.49c0,0.62-0.17,1.24-0.52,1.85
|
||||||
|
c-0.96-0.96-2.12-1.44-3.51-1.44H17C16.7,10.8,16.14,9.81,15.3,9z M16.92,3.73c0,0.24,0.08,0.44,0.25,0.61
|
||||||
|
c0.17,0.17,0.37,0.25,0.61,0.25c0.23,0,0.43-0.08,0.59-0.25c0.16-0.17,0.24-0.37,0.24-0.61V1.67c0-0.24-0.08-0.44-0.24-0.61
|
||||||
|
c-0.16-0.17-0.35-0.25-0.59-0.25c-0.24,0-0.44,0.08-0.61,0.25c-0.17,0.17-0.25,0.37-0.25,0.61V3.73z M22.47,6.02
|
||||||
|
c0,0.24,0.08,0.44,0.25,0.6c0.15,0.17,0.34,0.26,0.58,0.26c0.23,0,0.44-0.09,0.6-0.26l1.44-1.44c0.18-0.15,0.27-0.35,0.27-0.6
|
||||||
|
c0-0.24-0.09-0.44-0.26-0.61c-0.17-0.17-0.38-0.25-0.61-0.25c-0.22,0-0.41,0.09-0.57,0.27l-1.45,1.43
|
||||||
|
C22.56,5.57,22.47,5.78,22.47,6.02z M23.28,17.92c0,0.23,0.08,0.43,0.24,0.6l0.66,0.63c0.14,0.18,0.34,0.27,0.6,0.27
|
||||||
|
c0.24,0,0.43-0.09,0.57-0.27c0.18-0.16,0.27-0.36,0.27-0.6c0-0.24-0.09-0.44-0.27-0.61l-0.65-0.62c-0.16-0.18-0.35-0.26-0.58-0.26
|
||||||
|
s-0.43,0.08-0.6,0.25C23.36,17.48,23.28,17.69,23.28,17.92z M24.74,11.55c0,0.24,0.09,0.44,0.26,0.6c0.18,0.18,0.38,0.26,0.62,0.26
|
||||||
|
h2.03c0.24,0,0.44-0.08,0.61-0.25c0.17-0.17,0.26-0.37,0.26-0.61c0-0.23-0.08-0.43-0.25-0.59c-0.17-0.16-0.38-0.24-0.62-0.24h-2.03
|
||||||
|
c-0.25,0-0.46,0.08-0.63,0.24C24.83,11.12,24.74,11.32,24.74,11.55z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.5 KiB |
29
static/src/lib/weather-icons/svg/wi-day-lightning.svg
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M1.56,16.9c0,1.33,0.46,2.47,1.39,3.43c0.93,0.96,2.06,1.47,3.4,1.53c0.11,0,0.17-0.06,0.17-0.17v-1.37
|
||||||
|
c0-0.12-0.06-0.18-0.17-0.18c-0.87-0.07-1.6-0.41-2.19-1.04c-0.59-0.62-0.89-1.36-0.89-2.21c0-0.84,0.28-1.57,0.85-2.19
|
||||||
|
s1.26-0.97,2.1-1.04l0.52-0.08c0.13,0,0.2-0.06,0.2-0.17l0.06-0.51c0.11-1.08,0.56-1.99,1.37-2.71c0.81-0.73,1.76-1.09,2.86-1.09
|
||||||
|
c1.09,0,2.04,0.36,2.85,1.09c0.81,0.72,1.27,1.63,1.39,2.72l0.07,0.58c0,0.11,0.06,0.17,0.19,0.17h1.6c0.91,0,1.68,0.32,2.32,0.95
|
||||||
|
c0.64,0.63,0.96,1.39,0.96,2.28c0,0.85-0.3,1.59-0.89,2.21c-0.59,0.62-1.32,0.97-2.19,1.04c-0.13,0-0.2,0.06-0.2,0.18v1.37
|
||||||
|
c0,0.11,0.07,0.17,0.2,0.17c1.33-0.04,2.46-0.55,3.38-1.51s1.38-2.11,1.38-3.45c0-0.68-0.16-1.37-0.47-2.07
|
||||||
|
C22.6,13.89,23,12.8,23,11.56c0-0.71-0.14-1.39-0.42-2.04c-0.28-0.65-0.65-1.2-1.12-1.67s-1.03-0.84-1.67-1.12
|
||||||
|
c-0.65-0.27-1.32-0.41-2.03-0.41c-1.54,0-2.84,0.58-3.88,1.73c-0.86-0.41-1.74-0.62-2.65-0.62c-1.42,0-2.67,0.43-3.76,1.3
|
||||||
|
s-1.79,1.99-2.1,3.37c-1.1,0.26-2.01,0.83-2.73,1.73S1.56,15.75,1.56,16.9z M9.06,28.17h0.3l5.32-7.85
|
||||||
|
c0.04-0.04,0.05-0.09,0.02-0.14s-0.07-0.07-0.14-0.07h-2.18l2.3-4.21c0.07-0.14,0.03-0.22-0.14-0.22h-2.94
|
||||||
|
c-0.08,0-0.15,0.05-0.22,0.14l-2.16,5.72c-0.02,0.14,0.02,0.21,0.14,0.21h2.17L9.06,28.17z M9.94,4.59c0,0.25,0.08,0.46,0.24,0.62
|
||||||
|
l0.66,0.65c0.42,0.32,0.82,0.32,1.21,0c0.16-0.18,0.24-0.39,0.24-0.64c0-0.23-0.08-0.43-0.24-0.59l-0.64-0.65
|
||||||
|
c-0.19-0.17-0.39-0.25-0.62-0.25c-0.23,0-0.43,0.08-0.6,0.25C10.02,4.15,9.94,4.36,9.94,4.59z M15.28,9.02
|
||||||
|
C15.96,8.34,16.79,8,17.76,8c0.98,0,1.81,0.35,2.49,1.04c0.69,0.69,1.03,1.53,1.03,2.52c0,0.61-0.17,1.21-0.51,1.81
|
||||||
|
c-0.98-0.94-2.13-1.41-3.46-1.41h-0.31C16.74,10.82,16.16,9.84,15.28,9.02z M16.9,3.73c0,0.25,0.08,0.46,0.24,0.62
|
||||||
|
c0.16,0.16,0.36,0.24,0.61,0.24c0.24,0,0.43-0.08,0.59-0.24c0.16-0.16,0.23-0.37,0.23-0.62V1.69c0-0.24-0.08-0.43-0.23-0.59
|
||||||
|
c-0.16-0.16-0.35-0.23-0.59-0.23c-0.24,0-0.44,0.08-0.6,0.23S16.9,1.45,16.9,1.69V3.73z M22.42,6.05c0,0.23,0.09,0.43,0.27,0.6
|
||||||
|
c0.18,0.17,0.37,0.25,0.55,0.25c0.16,0,0.37-0.08,0.62-0.25l1.44-1.43c0.17-0.18,0.25-0.39,0.25-0.63c0-0.24-0.08-0.45-0.25-0.61
|
||||||
|
c-0.17-0.16-0.37-0.24-0.6-0.24c-0.22,0-0.41,0.08-0.58,0.25l-1.43,1.43C22.51,5.61,22.42,5.82,22.42,6.05z M23.23,17.87
|
||||||
|
c0,0.23,0.08,0.43,0.24,0.61l0.65,0.66c0.16,0.16,0.36,0.24,0.58,0.24c0.24,0,0.44-0.08,0.6-0.25c0.17-0.17,0.25-0.38,0.25-0.63
|
||||||
|
c0-0.23-0.08-0.42-0.25-0.57l-0.62-0.66c-0.19-0.16-0.39-0.23-0.62-0.23c-0.23,0-0.43,0.08-0.59,0.24
|
||||||
|
C23.31,17.44,23.23,17.64,23.23,17.87z M24.68,11.56c0,0.22,0.09,0.41,0.26,0.57c0.17,0.17,0.37,0.25,0.6,0.25h2.04
|
||||||
|
c0.24,0,0.44-0.08,0.61-0.24c0.17-0.16,0.25-0.35,0.25-0.59c0-0.24-0.09-0.44-0.26-0.61s-0.37-0.25-0.6-0.25h-2.04
|
||||||
|
c-0.23,0-0.43,0.08-0.6,0.25C24.77,11.12,24.68,11.32,24.68,11.56z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.0 KiB |
49
static/src/lib/weather-icons/svg/wi-day-rain-mix.svg
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M1.48,16.95c0,1.32,0.46,2.46,1.37,3.4c0.91,0.94,2.04,1.45,3.38,1.51c0.12,0,0.18-0.06,0.18-0.17v-1.33
|
||||||
|
c0-0.12-0.06-0.18-0.18-0.18c-0.86-0.04-1.58-0.38-2.17-1s-0.88-1.37-0.88-2.24c0-0.84,0.28-1.58,0.84-2.19
|
||||||
|
c0.56-0.62,1.26-0.96,2.1-1.03l0.53-0.08c0.11,0,0.16-0.05,0.16-0.14l0.08-0.55c0.12-1.09,0.59-2,1.38-2.72S10,9.16,11.1,9.16
|
||||||
|
s2.05,0.36,2.86,1.08c0.82,0.72,1.28,1.62,1.38,2.69l0.07,0.58c0.02,0.11,0.1,0.17,0.22,0.17h1.6c0.89,0,1.65,0.32,2.29,0.96
|
||||||
|
c0.64,0.64,0.96,1.41,0.96,2.31c0,0.87-0.29,1.61-0.88,2.24s-1.31,0.95-2.17,1c-0.13,0-0.2,0.06-0.2,0.18v1.33
|
||||||
|
c0,0.11,0.07,0.17,0.2,0.17c1.33-0.04,2.45-0.54,3.37-1.49c0.91-0.95,1.37-2.09,1.37-3.42c0-0.68-0.13-1.34-0.38-2.01
|
||||||
|
c0.78-0.96,1.16-2.08,1.16-3.35c0-0.71-0.14-1.38-0.41-2.03c-0.27-0.65-0.65-1.2-1.12-1.67s-1.03-0.84-1.67-1.12
|
||||||
|
s-1.33-0.42-2.04-0.42c-1.54,0-2.83,0.58-3.86,1.74c-0.89-0.44-1.81-0.66-2.74-0.66c-1.41,0-2.66,0.44-3.74,1.31s-1.77,2-2.08,3.38
|
||||||
|
c-1.12,0.26-2.04,0.83-2.75,1.73C1.83,14.76,1.48,15.79,1.48,16.95z M6.83,23.98c0,0.17,0.05,0.34,0.16,0.51
|
||||||
|
c0.11,0.17,0.27,0.28,0.47,0.35c0.23,0.07,0.45,0.06,0.64-0.04c0.2-0.09,0.33-0.28,0.4-0.56l0.14-0.61c0.05-0.23,0.02-0.44-0.1-0.63
|
||||||
|
c-0.12-0.2-0.29-0.33-0.52-0.4c-0.23-0.07-0.44-0.04-0.64,0.08S7.06,22.97,7,23.2l-0.14,0.59C6.84,23.85,6.83,23.91,6.83,23.98z
|
||||||
|
M7.6,21.08c0,0.22,0.08,0.41,0.24,0.57C8,21.83,8.19,21.91,8.4,21.91c0.24,0,0.44-0.08,0.6-0.24c0.17-0.16,0.25-0.35,0.25-0.59
|
||||||
|
c0-0.23-0.08-0.43-0.25-0.59c-0.17-0.16-0.37-0.24-0.6-0.24c-0.23,0-0.42,0.08-0.58,0.23S7.6,20.85,7.6,21.08z M8.21,18.81
|
||||||
|
c-0.01,0.16,0.03,0.31,0.13,0.45c0.1,0.15,0.26,0.25,0.48,0.32c0.21,0.06,0.41,0.04,0.62-0.07C9.65,19.4,9.79,19.23,9.86,19
|
||||||
|
l0.27-0.9c0.07-0.24,0.05-0.46-0.07-0.65c-0.12-0.19-0.3-0.32-0.54-0.39c-0.22-0.07-0.43-0.05-0.63,0.07
|
||||||
|
c-0.2,0.11-0.34,0.28-0.41,0.5l-0.24,0.92C8.22,18.71,8.21,18.8,8.21,18.81z M9.36,27.1c0,0.17,0.05,0.33,0.16,0.49
|
||||||
|
c0.11,0.16,0.27,0.27,0.49,0.33c0.09,0.02,0.17,0.03,0.24,0.03c0.43,0,0.7-0.2,0.8-0.61l0.13-0.59c0.06-0.26,0.03-0.48-0.08-0.68
|
||||||
|
s-0.29-0.32-0.52-0.37c-0.21-0.07-0.42-0.05-0.63,0.07c-0.21,0.12-0.34,0.29-0.41,0.51L9.4,26.88C9.37,26.99,9.36,27.07,9.36,27.1z
|
||||||
|
M9.92,4.66c0,0.24,0.08,0.44,0.24,0.6l0.66,0.64c0.14,0.16,0.32,0.24,0.54,0.26c0.22,0.02,0.43-0.07,0.62-0.26
|
||||||
|
c0.16-0.16,0.24-0.36,0.24-0.59c0-0.24-0.08-0.44-0.24-0.6l-0.63-0.65C11.2,3.9,11,3.82,10.77,3.8c-0.23,0-0.43,0.08-0.6,0.25
|
||||||
|
C10.01,4.22,9.92,4.42,9.92,4.66z M10.15,24.2c0,0.23,0.08,0.42,0.24,0.58c0.16,0.16,0.36,0.24,0.58,0.24
|
||||||
|
c0.24,0,0.43-0.08,0.59-0.23c0.16-0.16,0.23-0.35,0.23-0.59c0-0.23-0.08-0.42-0.23-0.58c-0.16-0.16-0.35-0.23-0.59-0.23
|
||||||
|
c-0.24,0-0.43,0.08-0.59,0.23S10.15,23.97,10.15,24.2z M10.77,21.93c-0.01,0.15,0.03,0.31,0.14,0.47c0.1,0.16,0.25,0.26,0.45,0.3
|
||||||
|
c0.23,0.06,0.44,0.04,0.64-0.06s0.33-0.29,0.41-0.56l0.26-0.9c0.07-0.22,0.05-0.43-0.07-0.63c-0.12-0.2-0.29-0.33-0.53-0.4
|
||||||
|
c-0.22-0.07-0.43-0.04-0.64,0.08s-0.34,0.3-0.41,0.53l-0.22,0.9C10.78,21.74,10.77,21.83,10.77,21.93z M13.53,24.08
|
||||||
|
c0,0.17,0.05,0.33,0.15,0.48c0.1,0.15,0.25,0.26,0.46,0.32c0.03,0,0.08,0.01,0.14,0.02c0.06,0.01,0.11,0.02,0.14,0.02
|
||||||
|
c0.41,0,0.66-0.22,0.76-0.66l0.14-0.6c0.07-0.21,0.05-0.42-0.07-0.63c-0.12-0.21-0.29-0.34-0.51-0.41
|
||||||
|
c-0.25-0.06-0.48-0.04-0.68,0.08s-0.34,0.29-0.41,0.53l-0.09,0.59c0,0.01,0,0.05-0.01,0.11C13.54,24,13.53,24.04,13.53,24.08z
|
||||||
|
M14.27,21.12c0,0.23,0.08,0.42,0.24,0.57c0.15,0.16,0.34,0.24,0.58,0.24s0.43-0.08,0.59-0.23c0.16-0.16,0.23-0.35,0.23-0.58
|
||||||
|
c0-0.24-0.08-0.43-0.23-0.59c-0.16-0.16-0.35-0.23-0.59-0.23s-0.43,0.08-0.59,0.23S14.27,20.88,14.27,21.12z M14.88,18.81
|
||||||
|
c0,0.17,0.05,0.33,0.16,0.48c0.11,0.15,0.27,0.26,0.49,0.32c0.02,0,0.06,0.01,0.12,0.02c0.06,0.01,0.11,0.02,0.14,0.02
|
||||||
|
c0.11,0,0.23-0.03,0.37-0.09c0.21-0.11,0.34-0.28,0.4-0.52l0.24-0.9c0.06-0.23,0.04-0.44-0.07-0.63s-0.28-0.33-0.5-0.4
|
||||||
|
c-0.23-0.07-0.45-0.05-0.64,0.06c-0.2,0.11-0.33,0.27-0.4,0.51l-0.28,0.91c0,0.02,0,0.05-0.01,0.11
|
||||||
|
C14.89,18.73,14.88,18.77,14.88,18.81z M15.23,9.09c0.66-0.66,1.48-0.99,2.47-0.99c0.99,0,1.83,0.34,2.52,1.02s1.04,1.5,1.04,2.48
|
||||||
|
c0,0.66-0.18,1.29-0.53,1.88c-0.98-0.98-2.15-1.47-3.5-1.47h-0.31C16.64,10.91,16.07,9.94,15.23,9.09z M16.88,3.83
|
||||||
|
c0,0.23,0.08,0.42,0.23,0.58c0.15,0.15,0.35,0.23,0.59,0.23c0.24,0,0.45-0.08,0.62-0.23c0.17-0.15,0.25-0.35,0.25-0.58V1.76
|
||||||
|
c0-0.23-0.09-0.43-0.26-0.6c-0.17-0.17-0.38-0.25-0.61-0.25c-0.23,0-0.43,0.08-0.58,0.25c-0.16,0.17-0.23,0.37-0.23,0.6V3.83z
|
||||||
|
M22.4,6.09c0,0.25,0.08,0.45,0.23,0.6c0.36,0.36,0.76,0.36,1.21,0l1.43-1.43c0.17-0.17,0.25-0.38,0.25-0.63
|
||||||
|
c0-0.24-0.08-0.44-0.25-0.6c-0.17-0.17-0.37-0.25-0.6-0.25c-0.23,0-0.43,0.08-0.61,0.24L22.63,5.5C22.48,5.65,22.4,5.84,22.4,6.09z
|
||||||
|
M23.18,17.94c0,0.23,0.09,0.43,0.27,0.59l0.61,0.63c0.2,0.16,0.4,0.24,0.61,0.24c0.2,0,0.4-0.08,0.6-0.24
|
||||||
|
c0.17-0.16,0.25-0.35,0.25-0.59c0-0.23-0.08-0.43-0.25-0.62l-0.65-0.61c-0.15-0.17-0.34-0.25-0.57-0.25s-0.43,0.08-0.6,0.25
|
||||||
|
C23.27,17.51,23.18,17.71,23.18,17.94z M24.66,11.6c0,0.24,0.09,0.43,0.26,0.59c0.18,0.18,0.39,0.27,0.62,0.27h2.03
|
||||||
|
c0.23,0,0.43-0.08,0.59-0.25c0.16-0.17,0.24-0.37,0.24-0.61c0-0.24-0.08-0.44-0.24-0.6c-0.16-0.17-0.35-0.25-0.59-0.25h-2.03
|
||||||
|
c-0.24,0-0.44,0.08-0.62,0.25S24.66,11.37,24.66,11.6z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.3 KiB |
35
static/src/lib/weather-icons/svg/wi-day-rain.svg
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M1.51,16.89c0,1.33,0.46,2.48,1.39,3.44s2.06,1.47,3.41,1.53c0.11,0,0.17-0.06,0.17-0.17v-1.34c0-0.11-0.06-0.17-0.17-0.17
|
||||||
|
c-0.86-0.04-1.59-0.39-2.19-1.03s-0.9-1.4-0.9-2.26c0-0.82,0.28-1.54,0.85-2.16s1.27-0.97,2.1-1.07l0.53-0.05
|
||||||
|
c0.13,0,0.2-0.06,0.2-0.17l0.07-0.54c0.11-1.08,0.56-1.99,1.37-2.72s1.76-1.1,2.85-1.1c1.09,0,2.04,0.37,2.86,1.1
|
||||||
|
s1.28,1.64,1.4,2.72l0.07,0.57c0,0.12,0.06,0.19,0.17,0.19h1.62c0.89,0,1.65,0.32,2.3,0.96c0.65,0.64,0.97,1.39,0.97,2.27
|
||||||
|
c0,0.87-0.3,1.62-0.9,2.26c-0.6,0.64-1.33,0.98-2.18,1.03c-0.12,0-0.19,0.06-0.19,0.17v1.34c0,0.11,0.06,0.17,0.19,0.17
|
||||||
|
c1.33-0.04,2.46-0.55,3.39-1.51c0.93-0.97,1.39-2.12,1.39-3.45c0-0.72-0.14-1.39-0.42-2.01c0.78-0.97,1.17-2.07,1.17-3.31
|
||||||
|
c0-0.95-0.24-1.83-0.71-2.64c-0.47-0.81-1.11-1.45-1.92-1.92s-1.68-0.7-2.62-0.7c-1.56,0-2.85,0.58-3.88,1.74
|
||||||
|
c-0.82-0.44-1.72-0.66-2.71-0.66c-1.41,0-2.67,0.44-3.76,1.32s-1.79,2-2.1,3.36c-1.11,0.26-2.02,0.84-2.74,1.74
|
||||||
|
S1.51,15.74,1.51,16.89z M6.91,23.75c0,0.17,0.05,0.33,0.16,0.49c0.11,0.16,0.27,0.27,0.49,0.33c0.11,0.02,0.2,0.04,0.27,0.04
|
||||||
|
c0.39,0,0.65-0.21,0.77-0.64l1.58-5.88c0.07-0.24,0.04-0.46-0.08-0.67c-0.12-0.21-0.3-0.33-0.53-0.38
|
||||||
|
c-0.22-0.07-0.43-0.05-0.63,0.07c-0.2,0.11-0.34,0.28-0.41,0.51l-1.58,5.91C6.93,23.66,6.91,23.73,6.91,23.75z M9.52,26.83
|
||||||
|
c0,0.19,0.05,0.36,0.15,0.52c0.1,0.16,0.27,0.26,0.52,0.3c0.11,0.02,0.2,0.04,0.26,0.04c0.16,0,0.31-0.06,0.45-0.17
|
||||||
|
c0.14-0.12,0.23-0.28,0.27-0.48l2.4-8.93c0.06-0.23,0.04-0.45-0.07-0.64s-0.28-0.33-0.5-0.4c-0.23-0.07-0.45-0.05-0.65,0.07
|
||||||
|
c-0.2,0.11-0.34,0.28-0.4,0.51l-2.4,8.93C9.53,26.73,9.52,26.82,9.52,26.83z M9.94,4.6c0,0.24,0.08,0.44,0.25,0.61l0.65,0.66
|
||||||
|
c0.19,0.15,0.4,0.22,0.62,0.22c0.21,0,0.41-0.08,0.58-0.23c0.17-0.16,0.26-0.35,0.26-0.59c0-0.24-0.08-0.46-0.24-0.64l-0.64-0.65
|
||||||
|
c-0.18-0.17-0.38-0.25-0.6-0.25c-0.24,0-0.45,0.09-0.62,0.26C10.03,4.16,9.94,4.37,9.94,4.6z M13.67,23.77
|
||||||
|
c0,0.16,0.05,0.32,0.15,0.47s0.26,0.26,0.46,0.32c0.11,0.02,0.2,0.04,0.25,0.04c0.17,0,0.34-0.05,0.49-0.15
|
||||||
|
c0.15-0.1,0.25-0.26,0.3-0.49l1.58-5.88c0.06-0.23,0.04-0.45-0.07-0.64c-0.11-0.2-0.28-0.33-0.5-0.4c-0.24-0.07-0.45-0.05-0.65,0.07
|
||||||
|
c-0.2,0.11-0.33,0.28-0.39,0.51l-1.58,5.91C13.69,23.68,13.67,23.76,13.67,23.77z M15.3,9.03c0.71-0.67,1.53-1,2.48-1
|
||||||
|
c0.98,0,1.82,0.35,2.5,1.04c0.69,0.69,1.03,1.53,1.03,2.52c0,0.62-0.17,1.24-0.52,1.85c-0.97-0.97-2.13-1.45-3.49-1.45h-0.33
|
||||||
|
C16.7,10.81,16.14,9.83,15.3,9.03z M16.92,3.78c0,0.23,0.08,0.43,0.25,0.59c0.17,0.16,0.37,0.24,0.61,0.24
|
||||||
|
c0.23,0,0.43-0.08,0.59-0.23c0.16-0.16,0.24-0.35,0.24-0.59V1.73c0-0.26-0.08-0.47-0.23-0.63c-0.16-0.16-0.35-0.24-0.59-0.24
|
||||||
|
c-0.25,0-0.46,0.08-0.62,0.25s-0.24,0.37-0.24,0.62V3.78z M22.45,6.06c0,0.24,0.09,0.44,0.27,0.59c0.14,0.16,0.32,0.24,0.55,0.26
|
||||||
|
c0.23,0.02,0.44-0.07,0.62-0.26l1.44-1.43c0.18-0.17,0.26-0.38,0.26-0.63c0-0.24-0.08-0.45-0.25-0.61
|
||||||
|
c-0.17-0.16-0.37-0.24-0.61-0.24c-0.21,0-0.4,0.08-0.58,0.25l-1.43,1.44C22.54,5.6,22.45,5.81,22.45,6.06z M23.26,17.91
|
||||||
|
c0,0.24,0.08,0.45,0.24,0.63l0.65,0.63c0.18,0.14,0.38,0.21,0.6,0.21l0.02,0.02c0.23,0,0.42-0.08,0.58-0.24
|
||||||
|
c0.16-0.16,0.24-0.37,0.24-0.61c0-0.24-0.09-0.43-0.26-0.58l-0.62-0.66c-0.18-0.16-0.39-0.24-0.62-0.24s-0.43,0.08-0.59,0.25
|
||||||
|
S23.26,17.67,23.26,17.91z M24.72,11.58c0,0.24,0.09,0.43,0.26,0.59c0.18,0.18,0.38,0.26,0.62,0.26h2.03c0.24,0,0.44-0.08,0.61-0.25
|
||||||
|
c0.17-0.17,0.25-0.37,0.25-0.6c0-0.24-0.08-0.44-0.25-0.61s-0.37-0.26-0.61-0.26H25.6c-0.24,0-0.44,0.09-0.62,0.26
|
||||||
|
C24.8,11.14,24.72,11.34,24.72,11.58z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.7 KiB |
43
static/src/lib/weather-icons/svg/wi-day-showers.svg
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M1.56,16.88c0,1.33,0.46,2.47,1.39,3.43s2.06,1.46,3.4,1.53c0.11,0,0.17-0.06,0.17-0.17v-1.34c0-0.12-0.06-0.18-0.17-0.18
|
||||||
|
c-0.85-0.04-1.57-0.38-2.17-1.02s-0.89-1.39-0.89-2.25c0-0.84,0.28-1.56,0.84-2.17s1.26-0.96,2.1-1.06l0.5-0.04
|
||||||
|
c0.13,0,0.2-0.06,0.2-0.18l0.06-0.53c0.11-1.08,0.56-1.99,1.37-2.71c0.81-0.73,1.76-1.09,2.86-1.09c1.09,0,2.05,0.36,2.86,1.09
|
||||||
|
c0.81,0.73,1.27,1.63,1.37,2.71l0.07,0.57c0,0.12,0.06,0.18,0.18,0.18h1.67c0.88,0,1.63,0.32,2.27,0.96
|
||||||
|
c0.64,0.64,0.96,1.39,0.96,2.27c0,0.85-0.3,1.59-0.9,2.22s-1.32,0.98-2.16,1.05c-0.11,0-0.17,0.06-0.17,0.18v1.34
|
||||||
|
c0,0.11,0.06,0.17,0.17,0.17c0.88-0.02,1.67-0.26,2.4-0.72s1.3-1.05,1.71-1.8c0.42-0.75,0.62-1.56,0.62-2.44
|
||||||
|
c0-0.71-0.14-1.37-0.41-1.96c0.76-0.94,1.13-2.03,1.13-3.28c0-0.71-0.14-1.39-0.41-2.04c-0.27-0.65-0.65-1.2-1.12-1.67
|
||||||
|
C21,7.46,20.44,7.09,19.8,6.82c-0.65-0.28-1.33-0.41-2.04-0.41c-1.51,0-2.78,0.55-3.81,1.66c-0.79-0.43-1.7-0.64-2.73-0.64
|
||||||
|
c-1.41,0-2.66,0.44-3.75,1.31s-1.77,1.99-2.07,3.35c-1.12,0.26-2.05,0.83-2.77,1.72C1.92,14.7,1.56,15.73,1.56,16.88z M6.97,23.58
|
||||||
|
c0,0.18,0.05,0.36,0.16,0.53c0.11,0.18,0.26,0.29,0.45,0.36c0.19,0.07,0.4,0.05,0.61-0.06c0.22-0.11,0.36-0.29,0.44-0.55l0.25-1.05
|
||||||
|
c0.07-0.21,0.05-0.41-0.07-0.62c-0.12-0.21-0.29-0.35-0.51-0.42c-0.25-0.06-0.47-0.03-0.67,0.08s-0.32,0.3-0.37,0.53l-0.28,0.99
|
||||||
|
C6.98,23.42,6.97,23.49,6.97,23.58z M8.28,18.86c0,0.38,0.21,0.64,0.64,0.79c0.22,0.08,0.43,0.06,0.64-0.05
|
||||||
|
c0.21-0.11,0.34-0.29,0.41-0.53l0.24-1.03c0.07-0.21,0.05-0.41-0.07-0.62c-0.11-0.21-0.28-0.35-0.51-0.42
|
||||||
|
c-0.24-0.06-0.47-0.04-0.67,0.08s-0.32,0.29-0.37,0.52l-0.3,1.02C8.29,18.7,8.28,18.78,8.28,18.86z M9.5,26.75
|
||||||
|
c0,0.16,0.06,0.33,0.17,0.5c0.11,0.17,0.28,0.29,0.49,0.36c0.01,0,0.04,0,0.1,0.01c0.06,0.01,0.11,0.01,0.15,0.01
|
||||||
|
c0.14,0,0.26-0.02,0.37-0.07c0.19-0.08,0.33-0.27,0.41-0.58l0.27-0.99c0.07-0.23,0.05-0.45-0.07-0.65c-0.12-0.2-0.29-0.34-0.51-0.4
|
||||||
|
c-0.23-0.07-0.45-0.05-0.65,0.07c-0.2,0.12-0.34,0.29-0.4,0.51l-0.28,1.02C9.51,26.63,9.5,26.7,9.5,26.75z M9.96,4.68
|
||||||
|
c0,0.25,0.08,0.46,0.25,0.62l0.66,0.65c0.34,0.34,0.73,0.34,1.17,0c0.16-0.17,0.24-0.38,0.24-0.61c0-0.23-0.08-0.43-0.24-0.61
|
||||||
|
l-0.63-0.66c-0.16-0.16-0.36-0.24-0.6-0.24c-0.23,0-0.43,0.08-0.6,0.25C10.04,4.24,9.96,4.44,9.96,4.68z M10.85,21.96
|
||||||
|
c0,0.17,0.05,0.34,0.16,0.51c0.11,0.17,0.26,0.28,0.47,0.35c0.23,0.07,0.44,0.05,0.64-0.05c0.19-0.1,0.33-0.29,0.4-0.56l0.24-1.01
|
||||||
|
c0.07-0.23,0.05-0.45-0.06-0.65c-0.11-0.2-0.28-0.34-0.5-0.41c-0.25-0.07-0.48-0.04-0.68,0.08c-0.2,0.12-0.33,0.3-0.37,0.53
|
||||||
|
l-0.28,1.03C10.85,21.81,10.85,21.87,10.85,21.96z M13.63,23.68c0.02,0.38,0.23,0.65,0.63,0.83l0.25,0.04
|
||||||
|
c0.16,0,0.32-0.05,0.47-0.16c0.15-0.11,0.26-0.27,0.32-0.5l0.29-1.01c0.06-0.24,0.03-0.46-0.09-0.66c-0.12-0.2-0.3-0.33-0.53-0.37
|
||||||
|
c-0.21-0.07-0.41-0.05-0.62,0.07s-0.34,0.29-0.41,0.51l-0.27,1.02c-0.01,0.02-0.01,0.05-0.02,0.08s-0.01,0.06-0.02,0.08
|
||||||
|
S13.63,23.66,13.63,23.68z M15.03,18.92c0,0.16,0.05,0.32,0.15,0.48c0.1,0.16,0.25,0.27,0.45,0.32l0.25,0.03
|
||||||
|
c0.19,0,0.37-0.06,0.52-0.18s0.24-0.28,0.28-0.47l0.27-0.99c0.07-0.24,0.05-0.45-0.07-0.65c-0.11-0.2-0.28-0.33-0.51-0.39
|
||||||
|
c-0.23-0.07-0.45-0.05-0.64,0.06c-0.2,0.11-0.33,0.28-0.39,0.5l-0.3,1.06C15.04,18.77,15.03,18.85,15.03,18.92z M15.36,9.06
|
||||||
|
c0.66-0.64,1.46-0.96,2.4-0.96c0.98,0,1.82,0.35,2.51,1.04c0.69,0.69,1.04,1.53,1.04,2.51c0,0.56-0.16,1.15-0.47,1.76
|
||||||
|
c-0.96-0.96-2.11-1.43-3.47-1.43h-0.34C16.77,10.84,16.21,9.87,15.36,9.06z M16.9,3.83c0,0.25,0.08,0.45,0.24,0.61
|
||||||
|
c0.16,0.16,0.36,0.24,0.61,0.24s0.45-0.08,0.61-0.24c0.16-0.16,0.24-0.36,0.24-0.61V1.81c0-0.25-0.08-0.46-0.24-0.62
|
||||||
|
c-0.16-0.16-0.36-0.24-0.61-0.24s-0.45,0.08-0.61,0.24c-0.16,0.16-0.24,0.37-0.24,0.62V3.83z M22.45,6.12
|
||||||
|
c0,0.25,0.08,0.45,0.23,0.61c0.21,0.17,0.41,0.25,0.62,0.25c0.19,0,0.38-0.08,0.59-0.25l1.43-1.43c0.16-0.18,0.24-0.39,0.24-0.63
|
||||||
|
c0-0.24-0.08-0.44-0.24-0.6c-0.16-0.16-0.36-0.24-0.59-0.24s-0.43,0.08-0.61,0.24L22.68,5.5C22.53,5.68,22.45,5.88,22.45,6.12z
|
||||||
|
M23.24,17.95c0,0.23,0.09,0.44,0.26,0.63l0.62,0.64c0.21,0.21,0.41,0.31,0.62,0.31c0.19,0,0.39-0.1,0.58-0.31
|
||||||
|
c0.18-0.18,0.27-0.39,0.26-0.61c-0.01-0.23-0.09-0.43-0.26-0.6l-0.65-0.66c-0.16-0.16-0.35-0.24-0.57-0.24
|
||||||
|
c-0.24,0-0.44,0.08-0.61,0.25C23.33,17.52,23.24,17.72,23.24,17.95z M24.71,11.64c0,0.22,0.08,0.42,0.24,0.58
|
||||||
|
c0.16,0.16,0.36,0.24,0.58,0.24h2.04c0.26,0,0.47-0.08,0.63-0.23c0.16-0.16,0.24-0.35,0.24-0.59c0-0.25-0.08-0.46-0.25-0.62
|
||||||
|
c-0.17-0.16-0.37-0.24-0.62-0.24h-2.04c-0.23,0-0.43,0.08-0.59,0.25C24.79,11.2,24.71,11.41,24.71,11.64z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.6 KiB |
40
static/src/lib/weather-icons/svg/wi-day-snow.svg
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M1.58,16.93c0,0.86,0.21,1.67,0.64,2.41c0.42,0.74,1,1.34,1.74,1.79c0.73,0.45,1.54,0.69,2.4,0.71
|
||||||
|
c0.11,0,0.17-0.06,0.17-0.17v-1.33c0-0.12-0.06-0.19-0.17-0.19c-0.85-0.04-1.58-0.38-2.18-1.02s-0.9-1.37-0.9-2.21
|
||||||
|
c0-0.82,0.28-1.54,0.85-2.16c0.57-0.61,1.26-0.97,2.1-1.07l0.53-0.06c0.12,0,0.18-0.06,0.18-0.19l0.08-0.51
|
||||||
|
c0.11-1.09,0.56-2,1.36-2.73c0.8-0.73,1.75-1.09,2.85-1.09c1.09,0,2.04,0.36,2.85,1.09c0.82,0.73,1.28,1.63,1.38,2.7l0.07,0.58
|
||||||
|
c0,0.11,0.06,0.17,0.17,0.17h1.61c0.9,0,1.67,0.32,2.31,0.96c0.64,0.64,0.96,1.4,0.96,2.29c0,0.84-0.3,1.57-0.9,2.21
|
||||||
|
c-0.6,0.63-1.33,0.97-2.17,1.02c-0.12,0-0.19,0.06-0.19,0.19v1.33c0,0.11,0.06,0.17,0.19,0.17c1.33-0.04,2.45-0.54,3.38-1.5
|
||||||
|
c0.93-0.96,1.39-2.09,1.39-3.41c0-0.76-0.14-1.43-0.43-2.03C22.6,13.95,23,12.85,23,11.6c0-0.94-0.23-1.81-0.7-2.61
|
||||||
|
c-0.47-0.8-1.11-1.44-1.91-1.91s-1.68-0.7-2.62-0.7c-1.54,0-2.83,0.58-3.87,1.73c-0.81-0.44-1.71-0.66-2.69-0.66
|
||||||
|
c-1.41,0-2.65,0.44-3.74,1.31s-1.78,1.99-2.09,3.34c-1.12,0.28-2.03,0.86-2.74,1.75C1.93,14.75,1.58,15.77,1.58,16.93z M7.92,20.98
|
||||||
|
c0,0.24,0.08,0.44,0.24,0.61c0.16,0.17,0.35,0.25,0.59,0.25c0.23,0,0.43-0.08,0.59-0.25c0.16-0.17,0.24-0.37,0.24-0.61
|
||||||
|
c0-0.23-0.08-0.42-0.24-0.58s-0.35-0.24-0.59-0.24c-0.23,0-0.43,0.08-0.59,0.24S7.92,20.76,7.92,20.98z M7.92,24.61
|
||||||
|
c0,0.21,0.08,0.4,0.24,0.57c0.18,0.16,0.37,0.24,0.58,0.24c0.24,0,0.43-0.08,0.59-0.23c0.16-0.16,0.23-0.35,0.23-0.58
|
||||||
|
c0-0.24-0.08-0.43-0.24-0.59c-0.16-0.16-0.35-0.23-0.59-0.23c-0.23,0-0.43,0.08-0.59,0.23C8,24.17,7.92,24.37,7.92,24.61z
|
||||||
|
M9.97,4.68c0,0.24,0.08,0.44,0.24,0.59l0.66,0.66c0.16,0.16,0.34,0.25,0.53,0.25c0.21,0.03,0.41-0.04,0.61-0.22
|
||||||
|
c0.2-0.18,0.3-0.39,0.3-0.63c0-0.24-0.08-0.46-0.24-0.64l-0.64-0.61c-0.15-0.17-0.34-0.25-0.58-0.25c-0.25,0-0.46,0.08-0.63,0.25
|
||||||
|
C10.05,4.24,9.97,4.44,9.97,4.68z M11.1,22.9c0,0.22,0.08,0.42,0.24,0.6c0.16,0.16,0.36,0.24,0.58,0.24c0.24,0,0.44-0.08,0.6-0.24
|
||||||
|
s0.25-0.36,0.25-0.6c0-0.23-0.08-0.43-0.25-0.6s-0.37-0.25-0.6-0.25c-0.23,0-0.42,0.08-0.58,0.25S11.1,22.67,11.1,22.9z M11.1,19.3
|
||||||
|
c0,0.23,0.08,0.42,0.24,0.58s0.36,0.24,0.58,0.24c0.24,0,0.44-0.08,0.6-0.24c0.17-0.16,0.25-0.35,0.25-0.59
|
||||||
|
c0-0.23-0.08-0.43-0.25-0.59s-0.37-0.24-0.6-0.24c-0.23,0-0.42,0.08-0.58,0.24S11.1,19.07,11.1,19.3z M11.1,26.56
|
||||||
|
c0,0.22,0.08,0.41,0.24,0.57c0.17,0.17,0.36,0.25,0.58,0.25c0.24,0,0.44-0.08,0.6-0.23c0.17-0.16,0.25-0.35,0.25-0.59
|
||||||
|
s-0.08-0.44-0.25-0.6c-0.17-0.17-0.37-0.25-0.6-0.25c-0.22,0-0.41,0.08-0.58,0.25C11.18,26.13,11.1,26.33,11.1,26.56z M14.32,20.98
|
||||||
|
c0,0.24,0.08,0.44,0.24,0.61c0.16,0.17,0.36,0.25,0.59,0.25s0.43-0.08,0.59-0.25c0.16-0.17,0.24-0.37,0.24-0.61
|
||||||
|
c0-0.23-0.08-0.42-0.24-0.58s-0.35-0.24-0.59-0.24s-0.43,0.08-0.59,0.24S14.32,20.76,14.32,20.98z M14.32,24.61
|
||||||
|
c0,0.21,0.08,0.4,0.23,0.57c0.18,0.16,0.38,0.24,0.6,0.24c0.24,0,0.43-0.08,0.59-0.23c0.16-0.16,0.23-0.35,0.23-0.58
|
||||||
|
c0-0.24-0.08-0.43-0.24-0.59c-0.16-0.16-0.35-0.23-0.59-0.23c-0.24,0-0.44,0.08-0.6,0.24C14.4,24.18,14.32,24.38,14.32,24.61z
|
||||||
|
M15.3,9.06c0.69-0.66,1.51-0.99,2.47-0.99c0.97,0,1.8,0.35,2.48,1.04c0.69,0.69,1.03,1.53,1.03,2.49c0,0.62-0.17,1.24-0.51,1.84
|
||||||
|
C19.82,12.48,18.66,12,17.3,12h-0.32C16.68,10.83,16.12,9.85,15.3,9.06z M16.9,3.84c0,0.23,0.08,0.43,0.25,0.58s0.37,0.23,0.61,0.23
|
||||||
|
s0.43-0.08,0.59-0.23c0.16-0.16,0.23-0.35,0.23-0.58V1.8c0-0.24-0.08-0.44-0.24-0.61S18,0.94,17.77,0.94s-0.43,0.09-0.6,0.26
|
||||||
|
c-0.17,0.17-0.26,0.37-0.26,0.6V3.84z M22.42,6.11c0,0.23,0.08,0.43,0.25,0.59c0.15,0.16,0.34,0.24,0.56,0.26s0.43-0.07,0.62-0.26
|
||||||
|
l1.43-1.43c0.18-0.18,0.26-0.38,0.26-0.61c0-0.24-0.09-0.44-0.26-0.61c-0.17-0.17-0.37-0.25-0.6-0.25c-0.22,0-0.41,0.08-0.58,0.25
|
||||||
|
l-1.43,1.46C22.5,5.67,22.42,5.87,22.42,6.11z M23.22,17.91c0,0.25,0.08,0.46,0.24,0.62l0.64,0.63c0.24,0.16,0.46,0.24,0.64,0.24
|
||||||
|
c0.21,0,0.39-0.09,0.56-0.26c0.17-0.17,0.25-0.38,0.25-0.61c0-0.23-0.09-0.42-0.26-0.58l-0.62-0.65c-0.18-0.16-0.38-0.24-0.61-0.24
|
||||||
|
s-0.43,0.08-0.59,0.25C23.3,17.47,23.22,17.67,23.22,17.91z M24.67,11.6c0,0.24,0.09,0.43,0.26,0.59c0.17,0.18,0.38,0.27,0.62,0.27
|
||||||
|
h2.02c0.23,0,0.43-0.08,0.6-0.25s0.25-0.37,0.25-0.61c0-0.24-0.08-0.44-0.25-0.6s-0.37-0.25-0.6-0.25h-2.02
|
||||||
|
c-0.24,0-0.44,0.08-0.62,0.25S24.67,11.37,24.67,11.6z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.3 KiB |
38
static/src/lib/weather-icons/svg/wi-day-storm-showers.svg
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M1.49,16.88c0,1.12,0.33,2.12,1,3s1.53,1.47,2.58,1.76l-0.66,1.7c-0.05,0.14,0,0.22,0.14,0.22h2.13l-1.43,4.21h0.29
|
||||||
|
l4.36-5.66c0.04-0.04,0.04-0.09,0.02-0.14C9.9,21.92,9.85,21.9,9.78,21.9H7.59l2.49-4.65c0.07-0.14,0.03-0.22-0.14-0.22H6.98
|
||||||
|
c-0.09,0-0.17,0.05-0.23,0.15l-1.07,2.88C4.96,19.88,4.36,19.5,3.9,18.9c-0.47-0.59-0.7-1.26-0.7-2.02c0-0.84,0.28-1.57,0.84-2.18
|
||||||
|
c0.56-0.61,1.27-0.97,2.11-1.07l0.51-0.03c0.12,0,0.19-0.05,0.22-0.14l0.07-0.59c0.11-1.08,0.56-1.99,1.37-2.72s1.76-1.1,2.86-1.1
|
||||||
|
c1.09,0,2.04,0.37,2.86,1.1s1.29,1.64,1.4,2.72l0.08,0.59c0,0.11,0.06,0.17,0.18,0.17h1.61c0.89,0,1.66,0.32,2.31,0.96
|
||||||
|
s0.97,1.4,0.97,2.29c0,0.87-0.3,1.62-0.9,2.26s-1.32,0.98-2.18,1.03c-0.13,0-0.2,0.06-0.2,0.18v1.34c0,0.11,0.07,0.17,0.2,0.17
|
||||||
|
c0.88-0.02,1.69-0.26,2.42-0.72c0.73-0.45,1.31-1.06,1.74-1.81s0.64-1.57,0.64-2.45c0-0.73-0.14-1.4-0.43-2.02
|
||||||
|
c0.76-0.96,1.14-2.06,1.14-3.29c0-0.95-0.24-1.83-0.71-2.64c-0.47-0.81-1.11-1.45-1.92-1.92c-0.81-0.47-1.69-0.71-2.64-0.71
|
||||||
|
c-0.72,0-1.42,0.15-2.1,0.45c-0.68,0.3-1.26,0.72-1.76,1.25c-0.81-0.43-1.71-0.65-2.72-0.65c-1.42,0-2.68,0.44-3.77,1.32
|
||||||
|
s-1.8,2-2.1,3.37c-1.11,0.26-2.02,0.84-2.74,1.74C1.85,14.7,1.49,15.73,1.49,16.88z M9.67,26.8c0,0.15,0.05,0.31,0.16,0.47
|
||||||
|
c0.11,0.16,0.26,0.27,0.46,0.34c0.11,0.03,0.2,0.04,0.25,0.04c0.15,0,0.28-0.03,0.38-0.08c0.21-0.08,0.36-0.27,0.43-0.57l0.27-1.03
|
||||||
|
c0.06-0.25,0.03-0.47-0.08-0.67s-0.3-0.32-0.53-0.37c-0.21-0.07-0.41-0.04-0.62,0.07c-0.21,0.12-0.35,0.29-0.42,0.52l-0.25,1.04
|
||||||
|
C9.69,26.7,9.67,26.78,9.67,26.8z M9.9,4.59c0,0.23,0.08,0.43,0.25,0.6l0.65,0.66c0.16,0.16,0.34,0.24,0.55,0.26
|
||||||
|
c0.21,0.03,0.41-0.04,0.61-0.23c0.2-0.18,0.3-0.39,0.3-0.64c0-0.23-0.08-0.43-0.25-0.6l-0.63-0.66c-0.16-0.16-0.36-0.24-0.6-0.24
|
||||||
|
c-0.25,0-0.46,0.08-0.63,0.24C9.99,4.16,9.9,4.36,9.9,4.59z M11.01,22c-0.01,0.16,0.04,0.32,0.14,0.47c0.1,0.15,0.26,0.26,0.48,0.32
|
||||||
|
c0.21,0.07,0.42,0.05,0.62-0.06c0.2-0.11,0.34-0.3,0.42-0.56l0.3-1.03c0.07-0.22,0.04-0.43-0.08-0.63s-0.3-0.34-0.54-0.41
|
||||||
|
c-0.23-0.07-0.44-0.05-0.64,0.07c-0.2,0.12-0.34,0.29-0.41,0.53l-0.24,1.05C11.03,21.9,11.01,21.98,11.01,22z M13.84,23.68
|
||||||
|
c0,0.14,0.03,0.28,0.1,0.39c0.13,0.21,0.31,0.36,0.54,0.43c0.11,0.04,0.21,0.06,0.28,0.06c0.13,0,0.23-0.02,0.31-0.08
|
||||||
|
c0.2-0.07,0.35-0.27,0.45-0.6l0.25-1.01c0.07-0.24,0.05-0.45-0.07-0.65c-0.11-0.2-0.28-0.33-0.51-0.39
|
||||||
|
c-0.23-0.07-0.45-0.05-0.65,0.07c-0.2,0.11-0.34,0.28-0.41,0.51l-0.28,1.04C13.85,23.53,13.84,23.61,13.84,23.68z M15.21,18.86
|
||||||
|
c0,0.18,0.05,0.34,0.16,0.5c0.11,0.16,0.27,0.27,0.49,0.33c0.17,0.06,0.37,0.04,0.61-0.05c0.2-0.09,0.34-0.28,0.43-0.57l0.27-1
|
||||||
|
c0.06-0.25,0.04-0.47-0.08-0.67s-0.29-0.32-0.53-0.37c-0.23-0.07-0.44-0.05-0.64,0.06c-0.2,0.11-0.33,0.28-0.4,0.5l-0.29,1.06
|
||||||
|
C15.22,18.79,15.21,18.86,15.21,18.86z M15.31,9.02c0.67-0.64,1.48-0.97,2.45-0.97c0.98,0,1.82,0.34,2.51,1.03
|
||||||
|
c0.69,0.68,1.04,1.52,1.04,2.5c0,0.66-0.16,1.26-0.47,1.81c-0.96-0.96-2.13-1.44-3.52-1.44h-0.31C16.72,10.76,16.15,9.78,15.31,9.02
|
||||||
|
z M16.91,3.75c0,0.24,0.08,0.44,0.25,0.61s0.37,0.25,0.6,0.25c0.24,0,0.44-0.08,0.6-0.25c0.16-0.17,0.24-0.37,0.24-0.61V1.69
|
||||||
|
c0-0.24-0.08-0.45-0.24-0.61C18.2,0.91,18,0.82,17.76,0.82c-0.24,0-0.44,0.08-0.6,0.25s-0.25,0.37-0.25,0.61V3.75z M22.49,6.04
|
||||||
|
c0,0.24,0.08,0.44,0.23,0.6c0.14,0.16,0.32,0.24,0.55,0.26c0.23,0.02,0.44-0.07,0.63-0.26l1.44-1.44c0.18-0.16,0.26-0.36,0.26-0.6
|
||||||
|
s-0.09-0.44-0.26-0.6c-0.16-0.18-0.36-0.26-0.6-0.26c-0.23,0-0.42,0.09-0.58,0.26l-1.44,1.44C22.56,5.59,22.49,5.79,22.49,6.04z
|
||||||
|
M23.26,17.95c0,0.23,0.08,0.43,0.25,0.6l0.65,0.63c0.18,0.17,0.39,0.25,0.62,0.25l0.02,0.02c0.22,0,0.4-0.09,0.54-0.27
|
||||||
|
c0.18-0.16,0.26-0.36,0.26-0.6c0-0.23-0.09-0.43-0.26-0.61l-0.62-0.62c-0.18-0.18-0.38-0.27-0.61-0.27c-0.24,0-0.44,0.09-0.6,0.26
|
||||||
|
C23.35,17.51,23.26,17.72,23.26,17.95z M24.73,11.58c0,0.24,0.09,0.44,0.26,0.59c0.16,0.18,0.36,0.26,0.6,0.26h2.06
|
||||||
|
c0.24,0,0.44-0.08,0.61-0.25c0.17-0.17,0.25-0.37,0.25-0.6s-0.08-0.44-0.25-0.6c-0.17-0.16-0.37-0.24-0.61-0.24h-2.06
|
||||||
|
c-0.24,0-0.45,0.08-0.61,0.24C24.81,11.14,24.73,11.34,24.73,11.58z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.2 KiB |
27
static/src/lib/weather-icons/svg/wi-day-sunny.svg
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M4.37,14.62c0-0.24,0.08-0.45,0.25-0.62c0.17-0.16,0.38-0.24,0.6-0.24h2.04c0.23,0,0.42,0.08,0.58,0.25
|
||||||
|
c0.15,0.17,0.23,0.37,0.23,0.61S8,15.06,7.85,15.23c-0.15,0.17-0.35,0.25-0.58,0.25H5.23c-0.23,0-0.43-0.08-0.6-0.25
|
||||||
|
C4.46,15.06,4.37,14.86,4.37,14.62z M7.23,21.55c0-0.23,0.08-0.43,0.23-0.61l1.47-1.43c0.15-0.16,0.35-0.23,0.59-0.23
|
||||||
|
c0.24,0,0.44,0.08,0.6,0.23s0.24,0.34,0.24,0.57c0,0.24-0.08,0.46-0.24,0.64L8.7,22.14c-0.41,0.32-0.82,0.32-1.23,0
|
||||||
|
C7.31,21.98,7.23,21.78,7.23,21.55z M7.23,7.71c0-0.23,0.08-0.43,0.23-0.61C7.66,6.93,7.87,6.85,8.1,6.85
|
||||||
|
c0.22,0,0.42,0.08,0.59,0.24l1.43,1.47c0.16,0.15,0.24,0.35,0.24,0.59c0,0.24-0.08,0.44-0.24,0.6s-0.36,0.24-0.6,0.24
|
||||||
|
c-0.24,0-0.44-0.08-0.59-0.24L7.47,8.32C7.31,8.16,7.23,7.95,7.23,7.71z M9.78,14.62c0-0.93,0.23-1.8,0.7-2.6s1.1-1.44,1.91-1.91
|
||||||
|
s1.67-0.7,2.6-0.7c0.7,0,1.37,0.14,2.02,0.42c0.64,0.28,1.2,0.65,1.66,1.12c0.47,0.47,0.84,1.02,1.11,1.66
|
||||||
|
c0.27,0.64,0.41,1.32,0.41,2.02c0,0.94-0.23,1.81-0.7,2.61c-0.47,0.8-1.1,1.43-1.9,1.9c-0.8,0.47-1.67,0.7-2.61,0.7
|
||||||
|
s-1.81-0.23-2.61-0.7c-0.8-0.47-1.43-1.1-1.9-1.9C10.02,16.43,9.78,15.56,9.78,14.62z M11.48,14.62c0,0.98,0.34,1.81,1.03,2.5
|
||||||
|
c0.68,0.69,1.51,1.04,2.49,1.04s1.81-0.35,2.5-1.04s1.04-1.52,1.04-2.5c0-0.96-0.35-1.78-1.04-2.47c-0.69-0.68-1.52-1.02-2.5-1.02
|
||||||
|
c-0.97,0-1.8,0.34-2.48,1.02C11.82,12.84,11.48,13.66,11.48,14.62z M14.14,22.4c0-0.24,0.08-0.44,0.25-0.6s0.37-0.24,0.6-0.24
|
||||||
|
c0.24,0,0.45,0.08,0.61,0.24s0.24,0.36,0.24,0.6v1.99c0,0.24-0.08,0.45-0.25,0.62c-0.17,0.17-0.37,0.25-0.6,0.25
|
||||||
|
s-0.44-0.08-0.6-0.25c-0.17-0.17-0.25-0.38-0.25-0.62V22.4z M14.14,6.9V4.86c0-0.23,0.08-0.43,0.25-0.6C14.56,4.09,14.76,4,15,4
|
||||||
|
s0.43,0.08,0.6,0.25c0.17,0.17,0.25,0.37,0.25,0.6V6.9c0,0.23-0.08,0.42-0.25,0.58S15.23,7.71,15,7.71s-0.44-0.08-0.6-0.23
|
||||||
|
S14.14,7.13,14.14,6.9z M19.66,20.08c0-0.23,0.08-0.42,0.23-0.56c0.15-0.16,0.34-0.23,0.56-0.23c0.24,0,0.44,0.08,0.6,0.23
|
||||||
|
l1.46,1.43c0.16,0.17,0.24,0.38,0.24,0.61c0,0.23-0.08,0.43-0.24,0.59c-0.4,0.31-0.8,0.31-1.2,0l-1.42-1.42
|
||||||
|
C19.74,20.55,19.66,20.34,19.66,20.08z M19.66,9.16c0-0.25,0.08-0.45,0.23-0.59l1.42-1.47c0.17-0.16,0.37-0.24,0.59-0.24
|
||||||
|
c0.24,0,0.44,0.08,0.6,0.25c0.17,0.17,0.25,0.37,0.25,0.6c0,0.25-0.08,0.46-0.24,0.62l-1.46,1.43c-0.18,0.16-0.38,0.24-0.6,0.24
|
||||||
|
c-0.23,0-0.41-0.08-0.56-0.24S19.66,9.4,19.66,9.16z M21.92,14.62c0-0.24,0.08-0.44,0.24-0.62c0.16-0.16,0.35-0.24,0.57-0.24h2.02
|
||||||
|
c0.23,0,0.43,0.09,0.6,0.26c0.17,0.17,0.26,0.37,0.26,0.6s-0.09,0.43-0.26,0.6c-0.17,0.17-0.37,0.25-0.6,0.25h-2.02
|
||||||
|
c-0.23,0-0.43-0.08-0.58-0.25S21.92,14.86,21.92,14.62z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
34
static/src/lib/weather-icons/svg/wi-day-thunderstorm.svg
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M1.52,16.9c0,1.11,0.33,2.09,0.98,2.96s1.51,1.46,2.57,1.78l-0.64,1.7c-0.04,0.14,0,0.21,0.14,0.21H6.7L5.45,27.5h0.29
|
||||||
|
l4.17-5.39c0.04-0.04,0.04-0.09,0.01-0.14C9.9,21.92,9.85,21.9,9.78,21.9H7.61l2.47-4.63c0.07-0.14,0.02-0.22-0.14-0.22H7
|
||||||
|
c-0.09,0-0.17,0.05-0.23,0.14L5.7,20.07c-0.71-0.18-1.3-0.57-1.77-1.16c-0.47-0.59-0.7-1.26-0.7-2.01c0-0.83,0.28-1.55,0.85-2.17
|
||||||
|
s1.27-0.97,2.1-1.07L6.7,13.6c0.13,0,0.2-0.06,0.2-0.18l0.06-0.51c0.11-1.08,0.57-1.99,1.38-2.72c0.81-0.73,1.77-1.1,2.86-1.1
|
||||||
|
c1.09,0,2.04,0.37,2.85,1.1s1.28,1.64,1.4,2.72l0.06,0.58c0,0.11,0.06,0.17,0.18,0.17h1.61c0.91,0,1.68,0.32,2.32,0.95
|
||||||
|
c0.64,0.63,0.96,1.39,0.96,2.29c0,0.85-0.3,1.59-0.89,2.21c-0.59,0.62-1.32,0.97-2.19,1.04c-0.13,0-0.2,0.06-0.2,0.18v1.37
|
||||||
|
c0,0.11,0.07,0.17,0.2,0.17c1.33-0.04,2.46-0.55,3.39-1.51c0.93-0.96,1.39-2.11,1.39-3.45c0-0.74-0.14-1.41-0.43-2.01
|
||||||
|
c0.79-0.96,1.18-2.06,1.18-3.32c0-0.94-0.24-1.81-0.71-2.62c-0.47-0.81-1.11-1.45-1.92-1.92c-0.81-0.47-1.68-0.71-2.62-0.71
|
||||||
|
c-1.54,0-2.84,0.58-3.88,1.73c-0.81-0.43-1.71-0.65-2.7-0.65c-1.41,0-2.67,0.44-3.76,1.31s-1.79,1.99-2.1,3.36
|
||||||
|
c-1.11,0.26-2.02,0.83-2.73,1.73S1.52,15.75,1.52,16.9z M9.61,26.48c-0.01,0.15,0.03,0.3,0.14,0.44s0.26,0.25,0.46,0.33
|
||||||
|
c0.07,0.02,0.14,0.03,0.21,0.03c0.17,0,0.34-0.05,0.51-0.15s0.28-0.26,0.34-0.47l2.29-8.57c0.06-0.23,0.04-0.45-0.07-0.64
|
||||||
|
c-0.11-0.2-0.27-0.33-0.49-0.4c-0.23-0.07-0.45-0.05-0.65,0.07c-0.2,0.11-0.34,0.28-0.4,0.51l-2.31,8.6
|
||||||
|
C9.62,26.3,9.61,26.39,9.61,26.48z M9.94,4.63c0,0.24,0.08,0.43,0.25,0.59l0.64,0.66C11,6.05,11.2,6.13,11.44,6.14
|
||||||
|
c0.24,0,0.43-0.08,0.57-0.26c0.19-0.15,0.28-0.35,0.28-0.6c0-0.24-0.08-0.43-0.25-0.59l-0.63-0.66c-0.17-0.16-0.38-0.24-0.61-0.24
|
||||||
|
c-0.25,0-0.46,0.08-0.62,0.24C10.02,4.19,9.94,4.39,9.94,4.63z M13.77,23.43c0,0.12,0.04,0.24,0.11,0.38
|
||||||
|
c0.13,0.2,0.29,0.34,0.5,0.43c0.07,0.03,0.17,0.05,0.3,0.05c0.15,0,0.26-0.02,0.33-0.06c0.2-0.08,0.34-0.28,0.41-0.58l1.49-5.55
|
||||||
|
c0.06-0.24,0.04-0.45-0.07-0.65c-0.11-0.19-0.28-0.32-0.51-0.39c-0.23-0.07-0.45-0.05-0.64,0.07c-0.2,0.11-0.33,0.28-0.39,0.51
|
||||||
|
L13.8,23.2c0,0.02-0.01,0.06-0.02,0.11C13.77,23.37,13.77,23.4,13.77,23.43z M15.3,9.04c0.67-0.64,1.49-0.97,2.48-0.97
|
||||||
|
c0.97,0,1.81,0.34,2.5,1.02c0.69,0.68,1.04,1.51,1.04,2.48c0,0.62-0.17,1.24-0.52,1.85c-0.99-0.98-2.16-1.47-3.5-1.47h-0.31
|
||||||
|
C16.68,10.78,16.11,9.81,15.3,9.04z M16.91,3.79c0,0.23,0.09,0.43,0.26,0.6s0.37,0.26,0.6,0.26c0.24,0,0.43-0.08,0.59-0.25
|
||||||
|
c0.16-0.17,0.23-0.37,0.23-0.61V1.73c0-0.24-0.08-0.44-0.23-0.61s-0.35-0.25-0.59-0.25c-0.23,0-0.43,0.08-0.6,0.25
|
||||||
|
s-0.26,0.37-0.26,0.61V3.79z M22.44,6.07c0,0.24,0.09,0.44,0.26,0.6c0.14,0.17,0.33,0.25,0.57,0.25s0.44-0.08,0.6-0.25l1.44-1.45
|
||||||
|
c0.17-0.16,0.26-0.35,0.26-0.59c0-0.24-0.08-0.44-0.25-0.61c-0.17-0.17-0.37-0.25-0.61-0.25c-0.22,0-0.41,0.09-0.57,0.26L22.7,5.47
|
||||||
|
C22.53,5.63,22.44,5.83,22.44,6.07z M23.25,17.93c0,0.22,0.08,0.42,0.24,0.6l0.66,0.63c0.12,0.14,0.31,0.23,0.54,0.24l0.01,0.01
|
||||||
|
c0.01,0,0.03,0,0.05,0c0.02,0,0.03,0,0.05,0c0.19,0,0.36-0.09,0.53-0.26c0.17-0.16,0.26-0.36,0.26-0.6c0-0.23-0.09-0.43-0.26-0.61
|
||||||
|
l-0.65-0.61c-0.16-0.18-0.36-0.27-0.58-0.27c-0.23,0-0.43,0.08-0.6,0.25C23.33,17.49,23.25,17.7,23.25,17.93z M24.7,11.58
|
||||||
|
c0,0.23,0.09,0.43,0.27,0.6c0.18,0.18,0.38,0.27,0.61,0.27h2.03c0.23,0,0.43-0.09,0.6-0.26s0.26-0.38,0.26-0.61
|
||||||
|
c0-0.23-0.08-0.43-0.25-0.59c-0.17-0.16-0.37-0.24-0.61-0.24h-2.03c-0.25,0-0.46,0.08-0.63,0.24C24.78,11.15,24.7,11.35,24.7,11.58z
|
||||||
|
"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.6 KiB |
30
static/src/lib/weather-icons/svg/wi-hail.svg
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M4.64,16.9c0,1.33,0.46,2.47,1.39,3.43c0.93,0.96,2.06,1.47,3.4,1.53c0.11,0,0.17-0.06,0.17-0.17v-1.34
|
||||||
|
c0-0.11-0.06-0.17-0.17-0.17c-0.86-0.04-1.58-0.38-2.18-1.02c-0.6-0.64-0.9-1.39-0.9-2.26c0-0.83,0.28-1.54,0.84-2.16
|
||||||
|
c0.56-0.61,1.26-0.97,2.09-1.07l0.53-0.03c0.13,0,0.2-0.06,0.2-0.19l0.06-0.53c0.11-1.08,0.56-1.99,1.37-2.71
|
||||||
|
c0.81-0.73,1.76-1.09,2.85-1.09c1.09,0,2.04,0.36,2.85,1.09c0.81,0.73,1.27,1.63,1.39,2.71l0.08,0.58c0,0.11,0.06,0.17,0.18,0.17
|
||||||
|
h1.61c0.89,0,1.66,0.32,2.31,0.96c0.65,0.64,0.98,1.39,0.98,2.27c0,0.87-0.3,1.62-0.9,2.26c-0.6,0.64-1.33,0.98-2.18,1.02
|
||||||
|
c-0.13,0-0.2,0.06-0.2,0.17v1.34c0,0.11,0.07,0.17,0.2,0.17c0.87-0.02,1.67-0.26,2.4-0.71c0.73-0.45,1.31-1.05,1.73-1.8
|
||||||
|
c0.42-0.75,0.63-1.57,0.63-2.44c0-0.89-0.22-1.72-0.67-2.47c-0.44-0.75-1.05-1.35-1.81-1.78S21.29,12,20.4,12h-0.32
|
||||||
|
c-0.32-1.34-1.03-2.43-2.1-3.28s-2.3-1.28-3.68-1.28c-1.41,0-2.66,0.44-3.75,1.31c-1.09,0.87-1.79,1.99-2.1,3.35
|
||||||
|
c-1.11,0.26-2.02,0.83-2.73,1.73S4.64,15.75,4.64,16.9z M10.09,24.1c0.09,0.21,0.25,0.37,0.46,0.46c0.2,0.1,0.41,0.11,0.62,0.02
|
||||||
|
c0.22-0.09,0.36-0.24,0.45-0.45c0.1-0.22,0.11-0.43,0.02-0.64c-0.08-0.21-0.24-0.35-0.45-0.44c-0.2-0.11-0.4-0.12-0.61-0.03
|
||||||
|
c-0.21,0.09-0.36,0.24-0.46,0.47C10.01,23.66,10.01,23.86,10.09,24.1z M10.72,21.28c0,0.16,0.05,0.31,0.15,0.45
|
||||||
|
c0.1,0.15,0.26,0.25,0.46,0.32c0.19,0.11,0.4,0.12,0.62,0.01c0.22-0.1,0.37-0.3,0.44-0.6l0.9-3.38c0.06-0.25,0.04-0.47-0.08-0.67
|
||||||
|
c-0.12-0.2-0.29-0.32-0.53-0.36c-0.08-0.02-0.16-0.03-0.24-0.03c-0.16,0-0.32,0.05-0.47,0.15c-0.15,0.1-0.26,0.25-0.32,0.44
|
||||||
|
l-0.88,3.39C10.73,21.16,10.72,21.25,10.72,21.28z M12.58,26.87c0,0.12,0.02,0.22,0.06,0.29c0.09,0.22,0.24,0.37,0.45,0.45
|
||||||
|
c0.09,0.05,0.2,0.08,0.33,0.08c0.06,0,0.16-0.02,0.3-0.06c0.22-0.08,0.38-0.23,0.47-0.45c0.1-0.22,0.1-0.44,0-0.66
|
||||||
|
c-0.1-0.22-0.25-0.37-0.45-0.46c-0.2-0.09-0.4-0.09-0.62,0c-0.19,0.08-0.32,0.2-0.41,0.36C12.62,26.58,12.58,26.73,12.58,26.87z
|
||||||
|
M13.31,24.26c0,0.37,0.21,0.61,0.63,0.73c0.11,0.03,0.19,0.04,0.24,0.04c0.15,0,0.28-0.03,0.38-0.08c0.21-0.08,0.35-0.27,0.42-0.57
|
||||||
|
l1.67-6.29c0.06-0.24,0.04-0.45-0.06-0.65c-0.1-0.19-0.27-0.32-0.49-0.38c-0.08-0.02-0.17-0.03-0.27-0.03
|
||||||
|
c-0.16,0-0.32,0.05-0.48,0.15c-0.16,0.1-0.26,0.25-0.3,0.44l-1.71,6.34C13.32,24.1,13.31,24.2,13.31,24.26z M16.74,23.8
|
||||||
|
c0,0.12,0.02,0.23,0.08,0.32c0.08,0.19,0.23,0.34,0.44,0.44c0.11,0.04,0.23,0.07,0.35,0.07c0.06,0,0.16-0.02,0.3-0.06
|
||||||
|
c0.21-0.08,0.37-0.23,0.46-0.44c0.07-0.22,0.07-0.43-0.01-0.63c-0.08-0.2-0.22-0.35-0.42-0.45c-0.23-0.11-0.44-0.12-0.65-0.03
|
||||||
|
c-0.21,0.09-0.36,0.24-0.46,0.47C16.77,23.59,16.74,23.69,16.74,23.8z M17.47,21.23c0,0.14,0.05,0.29,0.16,0.45
|
||||||
|
c0.11,0.16,0.26,0.27,0.45,0.33c0.16,0.03,0.25,0.05,0.27,0.05c0.09,0,0.22-0.03,0.37-0.1c0.2-0.09,0.33-0.27,0.4-0.52l0.9-3.34
|
||||||
|
c0.02-0.17,0.03-0.26,0.03-0.26c0-0.16-0.05-0.31-0.15-0.46c-0.1-0.15-0.25-0.25-0.45-0.31c-0.09-0.02-0.18-0.03-0.26-0.03
|
||||||
|
c-0.16,0-0.32,0.05-0.47,0.15s-0.25,0.25-0.31,0.45l-0.9,3.36L17.47,21.23z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
9
static/src/lib/weather-icons/svg/wi-lightning.svg
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M7.96,24.51h0.39l6.88-10.18c0.09-0.18,0.04-0.27-0.15-0.27h-2.84l2.99-5.45c0.09-0.18,0.02-0.27-0.2-0.27h-3.81
|
||||||
|
c-0.11,0-0.2,0.06-0.29,0.18l-2.78,7.4c-0.02,0.18,0.04,0.27,0.19,0.27h2.75L7.96,24.51z M16.46,18.18h0.27l5.22-7.67
|
||||||
|
c0.05-0.08,0.06-0.15,0.04-0.2s-0.08-0.07-0.17-0.07h-2.1l2.18-4.03c0.12-0.2,0.06-0.3-0.18-0.3h-2.74c-0.13,0-0.23,0.06-0.3,0.19
|
||||||
|
l-2.08,5.48c-0.03,0.09-0.03,0.16,0.01,0.21c0.04,0.05,0.1,0.07,0.19,0.07h2.04L16.46,18.18z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 806 B |
19
static/src/lib/weather-icons/svg/wi-night-alt-cloudy.svg
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M4.14,16.9c0-1.16,0.35-2.18,1.06-3.08s1.62-1.47,2.74-1.72c0.23-1.03,0.7-1.93,1.4-2.7c0.7-0.77,1.55-1.32,2.53-1.65
|
||||||
|
c0.62-0.21,1.26-0.32,1.93-0.32c0.81,0,1.6,0.16,2.35,0.48c0.28-0.47,0.61-0.88,0.99-1.22c0.38-0.34,0.77-0.61,1.17-0.79
|
||||||
|
c0.4-0.18,0.8-0.32,1.18-0.41s0.76-0.13,1.12-0.13c0.38,0,0.79,0.05,1.23,0.16l0.82,0.25c0.14,0.06,0.18,0.13,0.14,0.22l-0.14,0.6
|
||||||
|
c-0.07,0.31-0.1,0.6-0.1,0.86c0,0.31,0.05,0.63,0.15,0.95c0.1,0.32,0.24,0.63,0.44,0.94c0.19,0.31,0.46,0.58,0.8,0.83
|
||||||
|
c0.34,0.25,0.72,0.44,1.15,0.57l0.62,0.22c0.1,0.03,0.15,0.08,0.15,0.16c0,0.02-0.01,0.04-0.02,0.07l-0.18,0.67
|
||||||
|
c-0.27,1.08-0.78,1.93-1.5,2.57c0.4,0.7,0.62,1.45,0.65,2.24c0.01,0.05,0.01,0.12,0.01,0.23c0,0.89-0.22,1.72-0.67,2.48
|
||||||
|
c-0.44,0.76-1.05,1.36-1.8,1.8c-0.76,0.44-1.59,0.67-2.48,0.67H9.07c-0.89,0-1.72-0.22-2.48-0.67s-1.35-1.05-1.79-1.8
|
||||||
|
S4.14,17.8,4.14,16.9z M5.85,16.9c0,0.89,0.32,1.66,0.96,2.31c0.64,0.65,1.39,0.98,2.26,0.98h10.81c0.89,0,1.65-0.32,2.28-0.97
|
||||||
|
s0.95-1.42,0.95-2.32c0-0.88-0.32-1.63-0.96-2.26c-0.64-0.63-1.4-0.95-2.28-0.95h-1.78l-0.1-0.75c-0.1-1.01-0.52-1.88-1.26-2.59
|
||||||
|
s-1.62-1.11-2.63-1.2c-0.03,0-0.08,0-0.15-0.01c-0.07-0.01-0.11-0.01-0.15-0.01c-0.51,0-1.02,0.1-1.54,0.29V9.4
|
||||||
|
c-0.73,0.28-1.35,0.74-1.84,1.37c-0.5,0.63-0.8,1.35-0.9,2.17l-0.07,0.72l-0.68,0.03c-0.84,0.1-1.54,0.45-2.1,1.06
|
||||||
|
S5.85,16.07,5.85,16.9z M17.6,8.79c1.06,0.91,1.72,1.97,1.97,3.18h0.32c1.24,0,2.3,0.39,3.17,1.18c0.33-0.31,0.58-0.67,0.76-1.07
|
||||||
|
c-0.91-0.43-1.63-1.09-2.16-1.97c-0.52-0.88-0.79-1.81-0.79-2.78V7.09c-0.05-0.01-0.13-0.01-0.24-0.01
|
||||||
|
c-0.58-0.01-1.15,0.13-1.7,0.44C18.38,7.82,17.93,8.24,17.6,8.79z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
34
static/src/lib/weather-icons/svg/wi-night-alt-hail.svg
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M4.1,16.91c0,1.33,0.46,2.48,1.39,3.43s2.06,1.47,3.4,1.53c0.12,0,0.18-0.06,0.18-0.17v-1.34c0-0.11-0.06-0.17-0.18-0.17
|
||||||
|
c-0.86-0.04-1.58-0.38-2.18-1.02s-0.9-1.39-0.9-2.26c0-0.83,0.28-1.55,0.84-2.17c0.56-0.61,1.26-0.97,2.1-1.07l0.53-0.03
|
||||||
|
c0.13,0,0.2-0.06,0.2-0.18l0.07-0.54c0.11-1.08,0.56-1.99,1.37-2.72c0.81-0.73,1.76-1.1,2.85-1.1c1.08,0,2.03,0.37,2.85,1.1
|
||||||
|
c0.82,0.73,1.28,1.64,1.4,2.72l0.08,0.58c0,0.11,0.06,0.17,0.17,0.17h1.61c0.89,0,1.66,0.32,2.31,0.96c0.65,0.64,0.98,1.4,0.98,2.27
|
||||||
|
c0,0.87-0.3,1.62-0.9,2.26c-0.6,0.64-1.33,0.98-2.18,1.02c-0.13,0-0.2,0.06-0.2,0.17v1.34c0,0.11,0.07,0.17,0.2,0.17
|
||||||
|
c1.33-0.04,2.46-0.55,3.38-1.51c0.93-0.96,1.39-2.11,1.39-3.45c0-0.86-0.22-1.66-0.65-2.41c0.79-0.74,1.3-1.62,1.55-2.62l0.13-0.68
|
||||||
|
c0.02-0.01,0.03-0.03,0.03-0.07c0-0.07-0.05-0.13-0.16-0.16l-0.56-0.17c-0.57-0.17-1.05-0.45-1.46-0.85
|
||||||
|
c-0.4-0.4-0.69-0.81-0.86-1.25c-0.17-0.43-0.25-0.87-0.25-1.32c-0.01-0.24,0.02-0.51,0.08-0.79l0.14-0.58
|
||||||
|
c0.03-0.09-0.02-0.16-0.14-0.22l-0.8-0.25c-0.42-0.12-0.86-0.19-1.31-0.19c-0.35,0-0.71,0.04-1.08,0.13s-0.76,0.22-1.17,0.4
|
||||||
|
c-0.41,0.18-0.8,0.45-1.19,0.8c-0.38,0.35-0.72,0.75-1,1.22c-0.75-0.32-1.54-0.49-2.37-0.49c-1.41,0-2.67,0.44-3.76,1.31
|
||||||
|
s-1.79,1.99-2.1,3.36c-1.11,0.26-2.02,0.83-2.74,1.73S4.1,15.76,4.1,16.91z M9.58,23.94c0.09,0.21,0.24,0.36,0.46,0.45
|
||||||
|
c0.19,0.1,0.4,0.11,0.62,0.02c0.22-0.08,0.37-0.23,0.45-0.45c0.1-0.22,0.11-0.43,0.02-0.65c-0.08-0.21-0.23-0.36-0.45-0.44
|
||||||
|
c-0.2-0.1-0.41-0.11-0.62-0.02c-0.21,0.09-0.37,0.24-0.47,0.46C9.5,23.48,9.49,23.69,9.58,23.94z M10.2,21.11
|
||||||
|
c0,0.15,0.05,0.3,0.16,0.45s0.26,0.26,0.46,0.32c0.26,0.1,0.48,0.1,0.67,0c0.19-0.1,0.32-0.29,0.4-0.57l0.88-3.21
|
||||||
|
c0.07-0.25,0.04-0.47-0.08-0.67c-0.12-0.2-0.3-0.32-0.54-0.37c-0.22-0.07-0.43-0.05-0.63,0.07c-0.2,0.11-0.33,0.28-0.4,0.51
|
||||||
|
l-0.88,3.22c0,0.02-0.01,0.06-0.02,0.12C10.21,21.03,10.2,21.08,10.2,21.11z M12.07,26.71c0,0.12,0.02,0.22,0.06,0.29
|
||||||
|
c0.09,0.22,0.24,0.37,0.45,0.45c0.09,0.05,0.2,0.07,0.33,0.07c0.06,0,0.16-0.02,0.3-0.06c0.23-0.08,0.39-0.23,0.48-0.45
|
||||||
|
c0.1-0.22,0.1-0.44,0-0.66c-0.1-0.22-0.25-0.37-0.45-0.46c-0.2-0.09-0.4-0.09-0.61,0c-0.19,0.08-0.33,0.2-0.42,0.36
|
||||||
|
C12.11,26.42,12.07,26.57,12.07,26.71z M12.81,24.06c0,0.38,0.21,0.64,0.64,0.78c0.09,0.03,0.17,0.05,0.23,0.05
|
||||||
|
c0.11,0,0.23-0.03,0.35-0.08c0.23-0.08,0.39-0.27,0.47-0.57l1.65-6.12c0.06-0.24,0.04-0.45-0.07-0.65c-0.11-0.19-0.28-0.32-0.5-0.39
|
||||||
|
c-0.23-0.07-0.45-0.05-0.65,0.07c-0.2,0.11-0.34,0.28-0.4,0.51l-1.68,6.17C12.82,23.92,12.81,24,12.81,24.06z M16.25,23.64
|
||||||
|
c0,0.13,0.02,0.23,0.07,0.31c0.08,0.2,0.23,0.35,0.44,0.44c0.12,0.05,0.23,0.08,0.35,0.08c0.06,0,0.16-0.02,0.3-0.06
|
||||||
|
c0.22-0.09,0.37-0.23,0.45-0.44c0.08-0.22,0.08-0.43,0-0.63c-0.08-0.2-0.22-0.35-0.42-0.45c-0.22-0.1-0.44-0.11-0.65-0.02
|
||||||
|
c-0.22,0.08-0.37,0.24-0.47,0.46C16.27,23.41,16.25,23.51,16.25,23.64z M16.97,21.08c0,0.16,0.05,0.32,0.15,0.46
|
||||||
|
c0.1,0.14,0.25,0.25,0.45,0.31c0.17,0.02,0.26,0.03,0.27,0.03c0.41,0,0.66-0.2,0.77-0.61l0.87-3.17c0.06-0.24,0.04-0.45-0.07-0.65
|
||||||
|
c-0.11-0.19-0.28-0.32-0.5-0.39c-0.23-0.07-0.45-0.05-0.64,0.07c-0.2,0.11-0.33,0.28-0.4,0.51L17,20.81
|
||||||
|
C16.98,20.9,16.97,20.99,16.97,21.08z M17.62,8.83c0.31-0.57,0.75-1.01,1.3-1.31c0.55-0.3,1.14-0.45,1.76-0.44
|
||||||
|
c0.11,0,0.2,0.01,0.25,0.02v0.31c0,0.98,0.26,1.89,0.78,2.75c0.52,0.86,1.25,1.51,2.17,1.95c-0.19,0.44-0.44,0.79-0.75,1.07
|
||||||
|
C22.25,12.39,21.17,12,19.88,12h-0.32C19.3,10.75,18.66,9.69,17.62,8.83z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.6 KiB |
21
static/src/lib/weather-icons/svg/wi-night-alt-lightning.svg
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M4.12,16.91c0,1.33,0.46,2.48,1.39,3.43s2.06,1.47,3.4,1.53c0.11,0,0.17-0.06,0.17-0.17v-1.37c0-0.12-0.06-0.18-0.17-0.18
|
||||||
|
c-0.87-0.07-1.6-0.41-2.19-1.04c-0.59-0.62-0.89-1.36-0.89-2.21c0-0.84,0.28-1.57,0.85-2.19c0.57-0.62,1.26-0.97,2.1-1.04l0.53-0.07
|
||||||
|
c0.13,0,0.2-0.06,0.2-0.17l0.07-0.52c0.11-1.08,0.56-1.99,1.37-2.71c0.81-0.73,1.76-1.09,2.85-1.09c1.09,0,2.04,0.36,2.85,1.09
|
||||||
|
c0.81,0.72,1.27,1.63,1.39,2.72l0.07,0.58c0,0.12,0.06,0.18,0.18,0.18h1.61c0.91,0,1.68,0.32,2.32,0.95
|
||||||
|
c0.64,0.63,0.96,1.39,0.96,2.28c0,0.85-0.3,1.59-0.89,2.21c-0.59,0.62-1.32,0.97-2.19,1.04c-0.13,0-0.2,0.06-0.2,0.18v1.37
|
||||||
|
c0,0.11,0.07,0.17,0.2,0.17c1.33-0.04,2.46-0.55,3.38-1.51s1.38-2.11,1.38-3.45c0-0.89-0.23-1.72-0.68-2.48
|
||||||
|
c0.8-0.72,1.32-1.58,1.55-2.58l0.15-0.72c0.01-0.01,0.01-0.03,0.01-0.07c0-0.07-0.05-0.13-0.16-0.16l-0.58-0.17
|
||||||
|
c-0.57-0.16-1.05-0.44-1.45-0.82c-0.4-0.39-0.68-0.8-0.85-1.23c-0.17-0.43-0.25-0.87-0.25-1.32c0-0.24,0.03-0.51,0.09-0.79
|
||||||
|
l0.14-0.62c0.03-0.09-0.02-0.17-0.14-0.22l-0.79-0.24c-0.44-0.11-0.85-0.16-1.25-0.16c-0.36,0-0.73,0.04-1.12,0.13
|
||||||
|
c-0.38,0.09-0.78,0.22-1.19,0.41c-0.41,0.18-0.81,0.45-1.2,0.8c-0.39,0.35-0.72,0.75-1,1.22c-0.71-0.3-1.48-0.45-2.33-0.45
|
||||||
|
c-1.41,0-2.66,0.44-3.75,1.31c-1.09,0.87-1.79,1.99-2.1,3.35c-1.1,0.26-2.01,0.84-2.73,1.74C4.48,14.74,4.12,15.76,4.12,16.91z
|
||||||
|
M11.79,21.56c-0.05,0.14,0,0.22,0.14,0.22h2.16l-1.31,4.14h0.3l4.17-5.59c0.04-0.04,0.05-0.09,0.03-0.14
|
||||||
|
c-0.02-0.05-0.06-0.07-0.13-0.07h-2.2l2.31-4.21c0.07-0.14,0.02-0.22-0.14-0.22h-2.94c-0.08,0-0.15,0.05-0.22,0.14L11.79,21.56z
|
||||||
|
M17.6,8.81c0.33-0.57,0.77-1,1.33-1.3c0.55-0.3,1.13-0.45,1.72-0.45c0.13,0,0.22,0.01,0.27,0.02v0.31c0,0.96,0.26,1.87,0.78,2.73
|
||||||
|
c0.52,0.86,1.24,1.51,2.17,1.96c-0.16,0.37-0.41,0.73-0.75,1.07c-0.92-0.79-1.99-1.18-3.22-1.18h-0.32
|
||||||
|
C19.29,10.71,18.63,9.66,17.6,8.81z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
42
static/src/lib/weather-icons/svg/wi-night-alt-rain-mix.svg
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M4.07,16.93c0,1.33,0.47,2.47,1.4,3.43s2.07,1.47,3.4,1.51c0.12,0,0.18-0.06,0.18-0.17v-1.34c0-0.11-0.06-0.17-0.18-0.17
|
||||||
|
c-0.85-0.04-1.58-0.39-2.18-1.03s-0.91-1.39-0.91-2.23c0-0.85,0.28-1.59,0.85-2.21c0.57-0.62,1.27-0.97,2.1-1.04l0.53-0.07
|
||||||
|
c0.13,0,0.2-0.06,0.2-0.18l0.07-0.51c0.11-1.1,0.56-2.02,1.37-2.75c0.81-0.73,1.76-1.1,2.86-1.1c1.09,0,2.04,0.37,2.86,1.1
|
||||||
|
c0.82,0.73,1.29,1.64,1.4,2.72l0.07,0.57c0,0.12,0.06,0.19,0.17,0.19h1.62c0.91,0,1.68,0.32,2.33,0.97
|
||||||
|
c0.65,0.64,0.97,1.41,0.97,2.31c0,0.55-0.14,1.07-0.41,1.56s-0.65,0.89-1.13,1.2c-0.48,0.31-1,0.48-1.56,0.51
|
||||||
|
c-0.13,0-0.2,0.06-0.2,0.17v1.34c0,0.11,0.07,0.17,0.2,0.17c0.88-0.02,1.69-0.26,2.42-0.71c0.73-0.45,1.31-1.05,1.73-1.8
|
||||||
|
c0.42-0.75,0.63-1.56,0.63-2.43c0-0.88-0.23-1.72-0.68-2.51c0.83-0.74,1.36-1.62,1.58-2.62l0.14-0.68c0.02-0.02,0.03-0.04,0.03-0.07
|
||||||
|
c0-0.06-0.06-0.11-0.17-0.16l-0.55-0.18c-0.57-0.17-1.07-0.45-1.47-0.85c-0.41-0.4-0.7-0.81-0.87-1.25
|
||||||
|
c-0.17-0.43-0.26-0.86-0.26-1.29c-0.02-0.21,0.01-0.49,0.09-0.82l0.13-0.58c0.04-0.1,0-0.18-0.13-0.23l-0.8-0.24
|
||||||
|
c-0.41-0.11-0.84-0.17-1.29-0.17c-0.36,0-0.74,0.04-1.12,0.13c-0.38,0.09-0.78,0.22-1.19,0.41s-0.81,0.46-1.2,0.81
|
||||||
|
c-0.39,0.35-0.72,0.76-1,1.23c-0.81-0.31-1.6-0.46-2.35-0.46c-1.41,0-2.67,0.44-3.76,1.32s-1.8,2-2.11,3.37
|
||||||
|
c-1.12,0.29-2.04,0.88-2.75,1.77C4.42,14.74,4.07,15.77,4.07,16.93z M9.48,23.98c0,0.17,0.05,0.34,0.16,0.51
|
||||||
|
c0.11,0.17,0.27,0.28,0.47,0.35c0.23,0.07,0.44,0.06,0.64-0.04s0.32-0.28,0.39-0.56l0.14-0.61c0.05-0.23,0.02-0.44-0.09-0.63
|
||||||
|
c-0.11-0.2-0.28-0.33-0.52-0.4c-0.23-0.07-0.44-0.04-0.64,0.08s-0.34,0.3-0.4,0.53L9.5,23.79C9.48,23.83,9.48,23.89,9.48,23.98z
|
||||||
|
M10.24,21.08c0,0.21,0.08,0.4,0.25,0.57c0.16,0.17,0.34,0.25,0.56,0.25c0.24,0,0.44-0.08,0.6-0.24c0.16-0.16,0.24-0.35,0.24-0.59
|
||||||
|
c0-0.23-0.08-0.43-0.24-0.59c-0.16-0.16-0.36-0.24-0.6-0.24c-0.23,0-0.43,0.08-0.58,0.23S10.24,20.85,10.24,21.08z M10.85,18.81
|
||||||
|
c-0.01,0.16,0.03,0.31,0.14,0.45c0.1,0.15,0.26,0.25,0.48,0.32c0.21,0.06,0.41,0.04,0.62-0.07c0.21-0.11,0.34-0.28,0.41-0.51
|
||||||
|
l0.28-0.9c0.07-0.24,0.05-0.46-0.07-0.65c-0.12-0.19-0.3-0.32-0.54-0.39c-0.22-0.07-0.43-0.05-0.63,0.07
|
||||||
|
c-0.2,0.11-0.34,0.28-0.41,0.5l-0.24,0.92c0,0.02-0.01,0.06-0.02,0.12C10.85,18.72,10.85,18.77,10.85,18.81z M12.01,27.1
|
||||||
|
c0,0.18,0.05,0.34,0.15,0.5c0.1,0.16,0.26,0.27,0.48,0.33c0.08,0.02,0.17,0.03,0.25,0.03c0.43,0,0.69-0.2,0.79-0.61l0.14-0.59
|
||||||
|
c0.06-0.26,0.03-0.48-0.08-0.68s-0.29-0.32-0.52-0.37c-0.21-0.07-0.42-0.05-0.63,0.07c-0.21,0.12-0.34,0.29-0.41,0.51l-0.14,0.59
|
||||||
|
C12.02,26.97,12.01,27.04,12.01,27.1z M12.79,24.2c0,0.22,0.08,0.41,0.25,0.58c0.16,0.16,0.35,0.24,0.57,0.24
|
||||||
|
c0.24,0,0.43-0.08,0.59-0.23c0.16-0.16,0.23-0.35,0.23-0.59c0-0.23-0.08-0.42-0.23-0.58s-0.35-0.23-0.59-0.23s-0.43,0.08-0.59,0.23
|
||||||
|
C12.87,23.77,12.79,23.97,12.79,24.2z M13.42,21.93c-0.01,0.15,0.03,0.31,0.13,0.47s0.25,0.26,0.45,0.3
|
||||||
|
c0.23,0.06,0.44,0.04,0.64-0.06c0.19-0.1,0.33-0.29,0.41-0.56l0.27-0.9c0.07-0.22,0.05-0.43-0.07-0.63c-0.12-0.2-0.29-0.33-0.53-0.4
|
||||||
|
c-0.22-0.07-0.43-0.04-0.64,0.08s-0.34,0.3-0.41,0.53l-0.22,0.9C13.43,21.74,13.42,21.83,13.42,21.93z M16.17,24.08
|
||||||
|
c0,0.16,0.05,0.32,0.15,0.48s0.26,0.27,0.46,0.33c0.03,0,0.08,0.01,0.14,0.02c0.06,0.01,0.1,0.02,0.14,0.02
|
||||||
|
c0.41,0,0.66-0.22,0.76-0.66l0.14-0.6c0.07-0.21,0.05-0.42-0.07-0.63c-0.11-0.21-0.28-0.34-0.51-0.41
|
||||||
|
c-0.25-0.06-0.48-0.04-0.68,0.08s-0.34,0.29-0.41,0.53l-0.09,0.59c0,0.02-0.01,0.07-0.02,0.12C16.18,24,16.17,24.04,16.17,24.08z
|
||||||
|
M16.91,21.12c0,0.22,0.08,0.42,0.25,0.57c0.15,0.16,0.34,0.24,0.57,0.24c0.24,0,0.43-0.08,0.59-0.23s0.23-0.35,0.23-0.58
|
||||||
|
c0-0.24-0.08-0.43-0.23-0.59s-0.35-0.23-0.59-0.23c-0.24,0-0.43,0.08-0.59,0.23S16.91,20.88,16.91,21.12z M17.52,18.81
|
||||||
|
c0,0.17,0.05,0.33,0.16,0.48s0.27,0.26,0.49,0.32c0.02,0,0.06,0.01,0.12,0.02c0.06,0.01,0.11,0.02,0.14,0.02
|
||||||
|
c0.1,0,0.22-0.03,0.36-0.09c0.21-0.11,0.35-0.29,0.41-0.52l0.24-0.9c0.06-0.23,0.04-0.44-0.08-0.63c-0.11-0.2-0.28-0.33-0.51-0.4
|
||||||
|
c-0.23-0.07-0.44-0.05-0.64,0.06s-0.32,0.27-0.39,0.51l-0.28,0.91c0,0.02-0.01,0.06-0.02,0.12C17.53,18.74,17.52,18.78,17.52,18.81z
|
||||||
|
M17.59,8.76c0.32-0.58,0.76-1.02,1.31-1.34c0.56-0.32,1.13-0.47,1.73-0.46c0.09,0,0.19,0.01,0.3,0.03V7.3
|
||||||
|
c-0.01,0.98,0.25,1.9,0.77,2.76c0.53,0.86,1.27,1.5,2.22,1.94c-0.19,0.41-0.46,0.78-0.8,1.11c-0.92-0.76-2-1.14-3.23-1.14h-0.31
|
||||||
|
C19.27,10.66,18.61,9.59,17.59,8.76z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.5 KiB |
27
static/src/lib/weather-icons/svg/wi-night-alt-rain.svg
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M4.07,16.9c0,1.33,0.47,2.48,1.4,3.44s2.07,1.47,3.4,1.53c0.12,0,0.18-0.06,0.18-0.17v-1.34c0-0.11-0.06-0.17-0.18-0.17
|
||||||
|
c-0.86-0.05-1.59-0.39-2.19-1.03c-0.6-0.64-0.9-1.39-0.9-2.26c0-0.83,0.28-1.55,0.85-2.17c0.57-0.62,1.27-0.97,2.1-1.07l0.53-0.04
|
||||||
|
c0.13,0,0.2-0.06,0.2-0.17l0.07-0.54c0.11-1.08,0.57-1.99,1.38-2.72c0.81-0.73,1.77-1.1,2.86-1.1c1.09,0,2.04,0.37,2.86,1.1
|
||||||
|
c0.82,0.73,1.28,1.64,1.4,2.72l0.08,0.57c0,0.12,0.06,0.18,0.17,0.18h1.62c0.89,0,1.67,0.32,2.32,0.96c0.65,0.64,0.98,1.4,0.98,2.28
|
||||||
|
c0,0.87-0.3,1.62-0.9,2.26c-0.6,0.64-1.33,0.98-2.19,1.03c-0.14,0-0.21,0.06-0.21,0.17v1.34c0,0.11,0.07,0.17,0.21,0.17
|
||||||
|
c1.33-0.04,2.46-0.55,3.38-1.51c0.93-0.97,1.39-2.12,1.39-3.45c0-0.88-0.23-1.7-0.68-2.46c0.81-0.73,1.33-1.6,1.58-2.62l0.14-0.72
|
||||||
|
c0.01-0.01,0.02-0.03,0.02-0.07c0-0.07-0.05-0.13-0.16-0.16l-0.56-0.18c-0.57-0.16-1.06-0.44-1.46-0.83
|
||||||
|
c-0.41-0.39-0.7-0.8-0.87-1.23c-0.17-0.43-0.26-0.86-0.26-1.28c-0.02-0.22,0.01-0.5,0.08-0.82l0.14-0.61c0.04-0.1,0-0.18-0.14-0.24
|
||||||
|
l-0.79-0.24c-0.45-0.1-0.87-0.15-1.27-0.15c-0.38,0-0.76,0.04-1.14,0.13c-0.39,0.09-0.79,0.22-1.2,0.41
|
||||||
|
c-0.41,0.18-0.81,0.45-1.2,0.8c-0.39,0.35-0.72,0.75-1,1.22c-0.82-0.3-1.6-0.45-2.33-0.45c-1.41,0-2.67,0.44-3.76,1.32
|
||||||
|
s-1.8,2-2.11,3.37c-1.11,0.26-2.02,0.83-2.74,1.73C4.43,14.72,4.07,15.75,4.07,16.9z M9.63,23.74c0,0.17,0.05,0.33,0.16,0.49
|
||||||
|
c0.11,0.16,0.27,0.27,0.49,0.33c0.23,0.07,0.45,0.05,0.64-0.04c0.2-0.1,0.33-0.28,0.4-0.56l1.43-5.87c0.06-0.25,0.03-0.48-0.08-0.67
|
||||||
|
c-0.12-0.2-0.29-0.32-0.52-0.37c-0.22-0.07-0.43-0.05-0.63,0.07c-0.2,0.11-0.34,0.28-0.41,0.51l-1.44,5.9
|
||||||
|
c0,0.01-0.01,0.04-0.02,0.09C9.64,23.67,9.63,23.71,9.63,23.74z M12.24,26.81c0,0.16,0.05,0.31,0.15,0.46
|
||||||
|
c0.1,0.15,0.25,0.25,0.45,0.31c0.11,0.03,0.19,0.04,0.24,0.04c0.44,0,0.71-0.2,0.82-0.59l2.25-8.93c0.06-0.24,0.04-0.46-0.07-0.65
|
||||||
|
c-0.11-0.19-0.28-0.32-0.5-0.39c-0.23-0.07-0.45-0.05-0.66,0.07s-0.34,0.28-0.39,0.5l-2.26,8.92c0,0.01,0,0.05-0.01,0.12
|
||||||
|
C12.24,26.73,12.24,26.78,12.24,26.81z M16.4,23.82c0,0.36,0.21,0.6,0.63,0.74c0.14,0.04,0.24,0.06,0.3,0.06
|
||||||
|
c0.11,0,0.23-0.02,0.35-0.07c0.21-0.08,0.34-0.28,0.39-0.58l1.43-5.87c0.06-0.24,0.04-0.45-0.08-0.65
|
||||||
|
c-0.11-0.19-0.28-0.32-0.51-0.39c-0.23-0.07-0.45-0.05-0.66,0.07c-0.21,0.11-0.33,0.28-0.38,0.51l-1.43,5.9
|
||||||
|
C16.42,23.7,16.4,23.8,16.4,23.82z M17.58,8.77c0.32-0.58,0.75-1.02,1.31-1.33c0.55-0.3,1.14-0.45,1.76-0.44
|
||||||
|
c0.12,0,0.21,0,0.27,0.01v0.3c-0.01,0.97,0.24,1.88,0.77,2.75c0.52,0.86,1.26,1.52,2.21,1.97c-0.22,0.46-0.49,0.81-0.79,1.07
|
||||||
|
c-0.92-0.76-1.99-1.13-3.23-1.13h-0.31C19.3,10.7,18.64,9.64,17.58,8.77z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
34
static/src/lib/weather-icons/svg/wi-night-alt-showers.svg
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||||
|
<path d="M4.07,16.91c0,1.33,0.46,2.48,1.39,3.43s2.06,1.47,3.4,1.53c0.11,0,0.17-0.06,0.17-0.17v-1.37c0-0.12-0.06-0.18-0.17-0.18
|
||||||
|
c-0.87-0.07-1.6-0.41-2.19-1.04c-0.59-0.62-0.89-1.36-0.89-2.21c0-0.83,0.28-1.54,0.84-2.16s1.26-0.97,2.1-1.07l0.53-0.07
|
||||||
|
c0.13,0,0.2-0.06,0.2-0.17l0.07-0.52c0.11-1.08,0.56-1.99,1.37-2.71c0.81-0.73,1.76-1.09,2.85-1.09c1.09,0,2.04,0.36,2.85,1.09
|
||||||
|
c0.81,0.72,1.28,1.63,1.39,2.72l0.08,0.58c0,0.12,0.06,0.18,0.18,0.18h1.61c0.9,0,1.67,0.32,2.32,0.95
|
||||||
|
c0.64,0.63,0.97,1.39,0.97,2.28c0,0.85-0.3,1.59-0.89,2.21c-0.59,0.62-1.33,0.97-2.19,1.04c-0.13,0-0.2,0.06-0.2,0.18v1.37
|
||||||
|
c0,0.11,0.07,0.17,0.2,0.17c1.33-0.04,2.46-0.55,3.38-1.51c0.92-0.96,1.38-2.11,1.38-3.45c0-0.87-0.22-1.68-0.65-2.43
|
||||||
|
c0.81-0.73,1.34-1.6,1.58-2.62v-0.13l0.19-0.79l-0.76-0.21c-0.81-0.24-1.44-0.7-1.89-1.35c-0.45-0.66-0.67-1.34-0.67-2.03
|
||||||
|
c0-0.26,0.03-0.52,0.08-0.78l0.2-0.8l-0.85-0.25L21.9,5.49c-0.47-0.09-0.88-0.14-1.25-0.14c-0.38,0-0.76,0.04-1.14,0.13
|
||||||
|
c-0.39,0.09-0.79,0.22-1.2,0.41c-0.42,0.19-0.82,0.45-1.2,0.8c-0.38,0.35-0.72,0.76-1,1.23c-0.74-0.33-1.53-0.49-2.36-0.49
|
||||||
|
c-1.41,0-2.66,0.44-3.75,1.31s-1.77,1.99-2.07,3.36c-1.12,0.26-2.05,0.83-2.77,1.72C4.43,14.73,4.07,15.76,4.07,16.91z M9.47,23.68
|
||||||
|
c0,0.15,0.05,0.3,0.15,0.45c0.1,0.15,0.25,0.26,0.45,0.33c0.22,0.07,0.43,0.06,0.64-0.05s0.34-0.28,0.41-0.51l0.28-1.06
|
||||||
|
c0.07-0.21,0.05-0.41-0.07-0.62c-0.12-0.21-0.29-0.34-0.51-0.41c-0.23-0.06-0.45-0.03-0.65,0.08s-0.34,0.3-0.42,0.53l-0.23,0.99
|
||||||
|
C9.49,23.57,9.47,23.66,9.47,23.68z M10.77,18.95c0,0.11,0.03,0.23,0.1,0.36c0.07,0.17,0.25,0.3,0.53,0.38
|
||||||
|
c0.24,0.06,0.46,0.04,0.66-0.06c0.19-0.1,0.33-0.28,0.4-0.52l0.28-1.03c0.07-0.23,0.05-0.45-0.07-0.64
|
||||||
|
c-0.12-0.2-0.29-0.33-0.51-0.39c-0.24-0.06-0.47-0.04-0.67,0.07c-0.2,0.11-0.33,0.28-0.4,0.52l-0.27,1.01
|
||||||
|
C10.79,18.78,10.77,18.88,10.77,18.95z M12.02,26.8c0,0.17,0.05,0.33,0.15,0.49c0.1,0.16,0.25,0.27,0.45,0.33
|
||||||
|
c0.11,0.03,0.18,0.05,0.23,0.05c0.09,0,0.21-0.03,0.38-0.1c0.2-0.08,0.34-0.27,0.43-0.55l0.3-1.05c0.07-0.21,0.05-0.42-0.07-0.63
|
||||||
|
c-0.12-0.21-0.29-0.34-0.51-0.41c-0.24-0.06-0.47-0.04-0.67,0.08c-0.2,0.12-0.34,0.29-0.41,0.53l-0.25,1.01
|
||||||
|
C12.03,26.63,12.02,26.72,12.02,26.8z M13.35,22.03c0,0.15,0.05,0.3,0.15,0.45s0.25,0.26,0.46,0.33c0.22,0.07,0.44,0.05,0.64-0.06
|
||||||
|
c0.2-0.11,0.33-0.28,0.4-0.52l0.27-1.04c0.07-0.21,0.05-0.42-0.06-0.62c-0.11-0.2-0.27-0.34-0.49-0.41
|
||||||
|
c-0.24-0.06-0.47-0.03-0.68,0.08s-0.35,0.3-0.42,0.53l-0.24,1L13.35,22.03z M16.16,23.79c0,0.38,0.21,0.62,0.64,0.75
|
||||||
|
c0.09,0.02,0.17,0.03,0.24,0.03c0.15,0,0.27-0.02,0.37-0.07c0.21-0.08,0.36-0.27,0.44-0.57l0.27-1.02c0.06-0.25,0.04-0.47-0.08-0.67
|
||||||
|
s-0.29-0.32-0.53-0.37c-0.23-0.07-0.45-0.05-0.64,0.07s-0.33,0.29-0.4,0.51l-0.27,1.04c0,0.02-0.01,0.07-0.02,0.15
|
||||||
|
C16.16,23.71,16.16,23.76,16.16,23.79z M17.55,18.98c0,0.16,0.05,0.31,0.15,0.46c0.1,0.15,0.26,0.26,0.46,0.32
|
||||||
|
c0.14,0.03,0.22,0.05,0.23,0.05c0.09,0,0.21-0.03,0.38-0.1c0.21-0.08,0.35-0.27,0.44-0.55l0.28-1.04c0.06-0.22,0.03-0.43-0.08-0.63
|
||||||
|
s-0.3-0.33-0.53-0.4c-0.22-0.07-0.43-0.05-0.63,0.07s-0.33,0.29-0.4,0.52l-0.26,1.06C17.56,18.83,17.55,18.92,17.55,18.98z
|
||||||
|
M17.58,8.81c0.32-0.56,0.76-1,1.33-1.31c0.57-0.31,1.17-0.47,1.81-0.47h0.21c-0.01,0.09-0.01,0.21-0.01,0.38
|
||||||
|
c0,0.95,0.26,1.85,0.78,2.71c0.52,0.86,1.25,1.51,2.17,1.96c-0.22,0.43-0.48,0.8-0.78,1.1c-0.93-0.81-2.02-1.21-3.25-1.21h-0.32
|
||||||
|
C19.27,10.78,18.63,9.73,17.58,8.81z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.6 KiB |