diff --git a/docker-compose.yml b/docker-compose.yml index d9da5354e..8d00d5b60 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -52,6 +52,7 @@ services: - ./src/frontend:/app # One-way volume to use node_modules from inside image - /app/node_modules + - ./src/images/webcams:/app/images/webcams # depends_on: # - django command: npm run start-frontend diff --git a/src/backend/apps/webcam/serializers.py b/src/backend/apps/webcam/serializers.py index ce5e60e2d..75ff0a1d2 100644 --- a/src/backend/apps/webcam/serializers.py +++ b/src/backend/apps/webcam/serializers.py @@ -1,13 +1,9 @@ from pathlib import Path -import environ -from apps.webcam.models import Webcam +from django.conf import settings from rest_framework import serializers -# Base dir and env -BASE_DIR = Path(__file__).resolve().parents[4] -env = environ.Env() -environ.Env.read_env(BASE_DIR / '.env', overwrite=True) +from apps.webcam.models import Webcam class WebcamSerializer(serializers.ModelSerializer): @@ -22,12 +18,13 @@ class Meta: ) def get_links(self, obj): - proxy_root = env("DRIVEBC_IMAGE_PROXY_URL") + local_root = settings.DRIVEBC_IMAGE_BASE_URL + proxy_root = settings.DRIVEBC_IMAGE_PROXY_URL webcam_id = obj.id links = { "imageSource": f"{proxy_root}webcam/api/v1/webcams/{webcam_id}/imageSource", - "imageDisplay": f"{proxy_root}bchighwaycam/pub/cameras/{webcam_id}.jpg", + "imageDisplay": f"{local_root}images/{webcam_id}.jpg", "imageThumbnail": f"{proxy_root}bchighwaycam/pub/cameras/tn/{webcam_id}.jpg", "currentImage": f"{proxy_root}webcam/imageUpdate.php?cam={webcam_id}", diff --git a/src/backend/config/settings/drivebc.py b/src/backend/config/settings/drivebc.py index e32cc24f5..c5866a293 100644 --- a/src/backend/config/settings/drivebc.py +++ b/src/backend/config/settings/drivebc.py @@ -7,11 +7,19 @@ env = environ.Env() environ.Env.read_env(BASE_DIR / '.env', overwrite=True) +DEV_ENVIRONMENT = env.get_value("DEV_ENVIRONMENT", default=False) + +# Image Settings +DRIVEBC_IMAGE_API_BASE_URL = env("DRIVEBC_IMAGE_API_BASE_URL") +DRIVEBC_IMAGE_BASE_URL = env("DRIVEBC_IMAGE_BASE_URL") +DRIVEBC_IMAGE_PROXY_URL = env("DRIVEBC_IMAGE_PROXY_URL") + # Feed API Settings DRIVEBC_WEBCAM_API_BASE_URL = env("DRIVEBC_WEBCAM_API_BASE_URL") DRIVEBC_OPEN_511_API_BASE_URL = env("DRIVEBC_OPEN_511_API_BASE_URL") DRIVEBC_INLAND_FERRY_API_BASE_URL = env("DRIVEBC_INLAND_FERRY_API_BASE_URL") DRIVEBC_DIT_API_BASE_URL = env("DRIVEBC_DIT_API_BASE_URL") + # Weather API Settings WEATHER_CLIENT_ID = env("WEATHER_CLIENT_ID") WEATHER_CLIENT_SECRET = env("WEATHER_CLIENT_SECRET") diff --git a/src/backend/config/urls.py b/src/backend/config/urls.py index fb63f2a9e..9b2e27899 100644 --- a/src/backend/config/urls.py +++ b/src/backend/config/urls.py @@ -1,7 +1,7 @@ from apps.shared.views import static_override from django.conf import settings from django.contrib import admin -from django.urls import include, path +from django.urls import include, path, re_path urlpatterns = [ # django @@ -15,3 +15,9 @@ # TO BE REMOVED IN PRODUCTION ] + static_override(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + +if settings.DEV_ENVIRONMENT: + from django.views.static import serve + urlpatterns.append(re_path(r"^images/(?P.*)$", serve, { + "document_root": f'{settings.SRC_DIR}/images/webcams', + })) \ No newline at end of file diff --git a/src/frontend/src/Components/cameras/CameraCard.js b/src/frontend/src/Components/cameras/CameraCard.js index 00531e2ec..92e3181cb 100644 --- a/src/frontend/src/Components/cameras/CameraCard.js +++ b/src/frontend/src/Components/cameras/CameraCard.js @@ -68,13 +68,13 @@ export default function CameraCard(props) { {!unavailable && !delayed && !stale &&
- {camera.name} + {camera.name}
} {!unavailable && stale && !delayed &&
- {camera.name} + {camera.name}

Unable to retrieve latest image. Showing last image received.

@@ -96,7 +96,7 @@ export default function CameraCard(props) { {!unavailable && stale && delayed &&
- {camera.name} + {camera.name}

Longer than expected delay, displaying last image received.

diff --git a/src/frontend/src/Components/cameras/CameraCard.scss b/src/frontend/src/Components/cameras/CameraCard.scss index 9a91b88c5..df8ee52c9 100644 --- a/src/frontend/src/Components/cameras/CameraCard.scss +++ b/src/frontend/src/Components/cameras/CameraCard.scss @@ -25,6 +25,7 @@ position: relative; overflow: hidden; height: 180px; + margin-bottom: -10px; /* to allow overlap of image timestamp bar */ @media (min-width: 992px) { height: 180px; @@ -110,6 +111,7 @@ margin-bottom: 0.5rem; display: flex; justify-content: space-between; + position: relative; p { color: $White; diff --git a/src/frontend/src/Components/map/camPopup.js b/src/frontend/src/Components/map/camPopup.js index e6d8385d0..97c4532a6 100644 --- a/src/frontend/src/Components/map/camPopup.js +++ b/src/frontend/src/Components/map/camPopup.js @@ -95,7 +95,9 @@ export default function CamPopup(props) {
{camera.is_on ?
- +
+ +

DriveBC

diff --git a/src/frontend/src/Components/map/mapPopup.scss b/src/frontend/src/Components/map/mapPopup.scss index a65dcf9a8..ff6c81bc1 100644 --- a/src/frontend/src/Components/map/mapPopup.scss +++ b/src/frontend/src/Components/map/mapPopup.scss @@ -5,7 +5,7 @@ background-color: #F1F8FE; border-top: 4px solid #053662; padding: 1rem 1rem 0.5rem; - + .name { margin-top: 0.5rem; margin-bottom: 0; @@ -29,31 +29,36 @@ &__content { &__title { margin: 1rem 1rem 0.5rem; - + .name { font-size: 1.25rem; color: #053662; } } + .clip { + margin-bottom: -18px; /* for allowing overlap at bottom of cam image */ + } + &__image { img { width: 100%; } - + .timestamp { background-color: $Black; + position: relative; padding: 0 10px; display: flex; color: $White; align-items: baseline; - + p { color: $White; margin-bottom: 0; font-size: 0.625rem; } - + .driveBC { font-family: serif; span { @@ -61,16 +66,16 @@ } margin-right: 10px; } - + .friendly-time, .formatted-date { margin-left: auto; } - + .formatted-date { font-size: 0.75rem; } } - + .camera-unavailable { background-color: $Surface-status-red; padding: 1rem; @@ -80,11 +85,11 @@ color: $Type-status-red; clear: right; } - + svg { font-size: 1rem; } - + .card-pill { background-color: $Error; border-radius: 12px; @@ -94,7 +99,7 @@ justify-content: center; align-items: center; padding: 4px 6px; - + p { color: $White; font-weight: 700; @@ -130,7 +135,7 @@ .popup__title { background-color: #FBFFFC; border-top: 4px solid #2E8540; - + .name { color: #2E8540; a { @@ -142,7 +147,7 @@ } } } - + &__icon { background-color: #2E8540; color: white; @@ -163,7 +168,7 @@ } } } - + .popup__content { &__title { border-bottom: 1px solid $Divider; @@ -197,7 +202,7 @@ } } } - + &__block { display: flex; justify-content: space-between; @@ -208,14 +213,14 @@ } } - + &.major, &.closures { .popup__title { background-color: #F4E1E2; border-top: 4px solid #CE3E39; .name { - color: #CE3E39; + color: #CE3E39; } } @@ -265,7 +270,7 @@ .popup__title { background-color: #ECEAE8; border-top: 4px solid $Type-Primary; - + .name { color: $Type-Primary; } @@ -274,7 +279,7 @@ font-size: 0.875rem; color: $Grey70; } - + &__icon { background-color: $Type-Primary; color: white; @@ -349,11 +354,11 @@ .popup__title { background-color: #ECEAE8; border-top: 4px solid $Type-Primary; - + .name { color: $Type-Primary; } - + &__icon { background-color: $Type-Primary; color: white; @@ -427,7 +432,7 @@ flex-direction: column; flex-grow: 1; justify-content: space-between; - + p { text-align: center; color: $Type-Secondary; @@ -474,7 +479,7 @@ font-size: 0.875rem; margin-bottom: 0; } - + .label { text-align: left; font-weight: 700; diff --git a/src/frontend/src/pages/CameraDetailsPage.js b/src/frontend/src/pages/CameraDetailsPage.js index 2c710c57b..cd1070351 100644 --- a/src/frontend/src/pages/CameraDetailsPage.js +++ b/src/frontend/src/pages/CameraDetailsPage.js @@ -377,7 +377,7 @@ export default function CameraDetailsPage() { {camera.is_on && (
{!replay ? - camera : + camera :