From d0fa27898df1a220f89e2ccee260930a40e68337 Mon Sep 17 00:00:00 2001 From: Niels Rotmensen Date: Fri, 13 Jan 2023 14:42:07 +0100 Subject: [PATCH 1/6] Replace indexOf with an iterator variable since indexOf returned -1 on everything --- amelie/narrowcasting/static/js/promotion_widget.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/amelie/narrowcasting/static/js/promotion_widget.js b/amelie/narrowcasting/static/js/promotion_widget.js index daecebb..2a0739a 100644 --- a/amelie/narrowcasting/static/js/promotion_widget.js +++ b/amelie/narrowcasting/static/js/promotion_widget.js @@ -3,6 +3,7 @@ function PromotionWidget() { this.television_promotions = []; this.current_promotion = null; + this.current_idx = 0; this.ticks = 0; this.duration = 0; } @@ -36,12 +37,15 @@ PromotionWidget.prototype.tick = function () { PromotionWidget.prototype.change_photo = function () { if (this.television_promotions.length > 0){ - var res = this.television_promotions.indexOf(this.current_promotion); - if (res >= 0) { - this.current_promotion = this.television_promotions[(res + 1) % (this.television_promotions.length)]; + if (this.current_idx >= 0) { + var idx = (this.current_idx + 1) % this.television_promotions.length; + + this.current_promotion = this.television_promotions[idx]; + this.current_idx = idx; } else if (this.television_promotions.length > 0) { this.current_promotion = this.television_promotions[0]; + this.current_idx = 0; } var photo_url = this.current_promotion.image; From 1e0ead202a0cf1ae05c56ab4f4d4140446c72e87 Mon Sep 17 00:00:00 2001 From: Niels Rotmensen Date: Mon, 16 Jan 2023 14:46:11 +0100 Subject: [PATCH 2/6] Change title to summary --- amelie/api/narrowcasting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/amelie/api/narrowcasting.py b/amelie/api/narrowcasting.py index e740bce..5f873f2 100644 --- a/amelie/api/narrowcasting.py +++ b/amelie/api/narrowcasting.py @@ -54,13 +54,13 @@ def get_television_promotions(request): result = [] promotions = TelevisionPromotion.objects.filter(start__lte=timezone.now(), end__gte=timezone.now()) - for promotion in promotions: + for promotion in promotions: res_dict = { "image": "%s%s" % (settings.MEDIA_URL, str(promotion.attachment)) } if promotion.activity: - res_dict["title"] = promotion.activity.description + res_dict["title"] = promotion.activity.summary if promotion.activity.enrollment: res_dict["signup"] = promotion.activity.enrollment res_dict["signupStart"] = promotion.activity.enrollment_begin.isoformat() From 73d6e55e996c509a992b0f7efb6b61d13f08c9ff Mon Sep 17 00:00:00 2001 From: Niels Rotmensen Date: Mon, 16 Jan 2023 14:47:04 +0100 Subject: [PATCH 3/6] Start working on reworking tv narrowcasting --- .../static/js/promotion_widget.js | 19 ++- .../static/js/tv_narrowcasting.js | 135 ++++++++++++++++++ amelie/narrowcasting/templates/index.html | 19 +-- 3 files changed, 157 insertions(+), 16 deletions(-) create mode 100644 amelie/narrowcasting/static/js/tv_narrowcasting.js diff --git a/amelie/narrowcasting/static/js/promotion_widget.js b/amelie/narrowcasting/static/js/promotion_widget.js index 2a0739a..9421e6f 100644 --- a/amelie/narrowcasting/static/js/promotion_widget.js +++ b/amelie/narrowcasting/static/js/promotion_widget.js @@ -58,12 +58,16 @@ PromotionWidget.prototype.change_photo = function () { }).load(function(){ $(".photo-wrapper #photo").remove(); $(".photo-wrapper").append($(this)); - if(self.current_promotion.title != undefined){ - $("#photo-activity").html(self.current_promotion.title); - } else { - // This is a hack, hiding and showing does not yet work correctly - $("#photo-activity").html("Promotion"); - } + + // if(self.current_promotion.title != undefined){ + // $("#photo-activity").html(self.current_promotion.title); + // } else { + // // This is a hack, hiding and showing does not yet work correctly + // $("#photo-activity").html("Promotion"); + // } + + $("#photo-activity").hide(); + if($(this).height() > $(this).width()){ $("#photo").addClass("portrait"); } @@ -75,4 +79,5 @@ PromotionWidget.prototype.change_photo = function () { } }; -PromotionWidget.prototype.get_duration = function () { return this.duration; }; +// PromotionWidget.prototype.get_duration = function () { return this.duration; }; +PromotionWidget.prototype.get_duration = function () { return 5; }; diff --git a/amelie/narrowcasting/static/js/tv_narrowcasting.js b/amelie/narrowcasting/static/js/tv_narrowcasting.js new file mode 100644 index 0000000..7551a43 --- /dev/null +++ b/amelie/narrowcasting/static/js/tv_narrowcasting.js @@ -0,0 +1,135 @@ +// Constants +const SECOND = 1000 +const MINUTE = 60 * SECOND +const SWAP_TIME = 20 * SECOND + +// Elements +let date +let time +let activityTable + +window.addEventListener('load', () => { + // Initializes the screen + date = document.querySelector('#date') + time = document.querySelector('#time') + activityTable = document.querySelector('#activity-table') + + // Update state + update(updateBannerState, 5*MINUTE) + update(updateActivityState, 5*MINUTE) + + // Update UI + update(updateDateTimeUI, SECOND) +}) + +/* + Helper functions +*/ + +const update = (callback, interval) => { + callback() + setInterval(callback, interval) +} + +/** + * Do an JSON-RPC request to the backend + * @param {string} endpoint + * @param {[any]} params + */ +const req = async (endpoint, params) => { + const body = { + jsonrpc: '2.0', + id: 1, + method: endpoint, + params + } + + return fetch('/api/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(body) + }) + .then(res => res.json()) + .then(res => { + if (res.error) { + throw new Error(res.error.message) + } + + return res.result + }) +} + +/* + Update state +*/ +const updateBannerState = () => { + req('getBanners', []) + .then(updateBannerUI) + .catch(console.error) +} + +const updateActivityState = () => { + req('getUpcomingActivities', [4]) + .then(updateActivityUI) + .catch(console.error) +} + +/* + Update UI elements +*/ +const updateDateTimeUI = () => { + const currentDate = new Date() + + date.textContent = currentDate.toLocaleDateString('en-GB', { + weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' + }) + time.textContent = currentDate.toLocaleTimeString('en-GB', { + hour: '2-digit', minute: '2-digit', second: '2-digit' + }) +} + + +const updateActivityUI = (activities) => { + if (activities.length > 0) { + // First remove all old activities + activityTable.textContent = '' + + const options = { + weekday: "short", month: "short", + day: "numeric", hour: "2-digit", minute: "2-digit" + } + + const rows = [] + + // Now generate the new table + activities.forEach(activity => { + const date = new Date(activity['beginDate']).toLocaleString('en-GB', options) + + const elem = ` + + + + + + ${date} + + + ${activity.title} + + + ` + + rows.push(elem) + }) + + activityTable.innerHTML = rows.join('') + } else { + // No events on website, keep the current one and we'll check again later + } +} + +const updateBannerUI = (banners) => { + console.log(banners) +} \ No newline at end of file diff --git a/amelie/narrowcasting/templates/index.html b/amelie/narrowcasting/templates/index.html index ebb6b5c..bab5ae6 100644 --- a/amelie/narrowcasting/templates/index.html +++ b/amelie/narrowcasting/templates/index.html @@ -52,13 +52,14 @@ {% elif theme == "valentine" %} {% endif %} - - - - - - - + + + + + + + +
@@ -79,8 +80,8 @@ {% endif %} {% endif %}
-
-
+
+
From e97b783d973b1815403b2bf7cd43a5b0cfe2c13a Mon Sep 17 00:00:00 2001 From: Niels Rotmensen Date: Mon, 6 Feb 2023 22:42:57 +0100 Subject: [PATCH 4/6] Add TelevisionVideo model --- amelie/companies/admin.py | 6 +++- .../migrations/0006_televisionvideo.py | 29 +++++++++++++++++++ amelie/companies/models.py | 18 ++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 amelie/companies/migrations/0006_televisionvideo.py diff --git a/amelie/companies/admin.py b/amelie/companies/admin.py index 701da7e..5555bf5 100644 --- a/amelie/companies/admin.py +++ b/amelie/companies/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from amelie.companies.models import Company, TelevisionBanner, WebsiteBanner +from amelie.companies.models import Company, TelevisionBanner, WebsiteBanner, TelevisionVideo class CompanyAdmin(admin.ModelAdmin): @@ -14,8 +14,12 @@ class WebsiteBannerAdmin(admin.ModelAdmin): class TelevisionBannerAdmin(admin.ModelAdmin): list_display = ['name', 'start_date', 'end_date', 'active'] + +class TelevisionVideoAdmin(admin.ModelAdmin): + list_display = ['name', 'start_date', 'end_date', 'active'] admin.site.register(Company, CompanyAdmin) admin.site.register(WebsiteBanner, WebsiteBannerAdmin) admin.site.register(TelevisionBanner, TelevisionBannerAdmin) +admin.site.register(TelevisionVideo, TelevisionVideoAdmin) diff --git a/amelie/companies/migrations/0006_televisionvideo.py b/amelie/companies/migrations/0006_televisionvideo.py new file mode 100644 index 0000000..5907f5b --- /dev/null +++ b/amelie/companies/migrations/0006_televisionvideo.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.16 on 2023-02-06 21:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('companies', '0005_add_default_career_label'), + ] + + operations = [ + migrations.CreateModel( + name='TelevisionVideo', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('start_date', models.DateField()), + ('end_date', models.DateField()), + ('active', models.BooleanField(default=True)), + ('video_id', models.CharField(max_length=12)), + ], + options={ + 'verbose_name': 'Television Promotion Video', + 'verbose_name_plural': 'Television Promotion Videos', + 'ordering': ['-start_date'], + }, + ), + ] diff --git a/amelie/companies/models.py b/amelie/companies/models.py index 4723251..3d6965a 100644 --- a/amelie/companies/models.py +++ b/amelie/companies/models.py @@ -9,6 +9,7 @@ from amelie.companies.managers import CompanyManager from amelie.calendar.managers import EventManager from amelie.calendar.models import Event +from amelie.activities.models import ActivityLabel from amelie.tools.discord import send_discord class Company(models.Model): @@ -175,6 +176,19 @@ class Meta: ordering = ['-end_date'] verbose_name = _('Television banner') verbose_name_plural = _('Television banners') + + +class TelevisionVideo(models.Model): + name = models.CharField(max_length=100) + start_date = models.DateField() + end_date = models.DateField() + active = models.BooleanField(default=True) + video_id = models.CharField(max_length=12) + + class Meta: + ordering = ['-start_date'] + verbose_name = _('Television Promotion Video') + verbose_name_plural = _('Television Promotion Videos') class CompanyEvent(Event): @@ -186,6 +200,10 @@ class CompanyEvent(Event): visible_from = models.DateTimeField() visible_till = models.DateTimeField() + @property + def activity_label(self): + return ActivityLabel.objects.filter(name_en="Career").first() + @property def activity_type(self): return "external" From 0bebfd2ff75f6d6be25b585283272e5bdac8b4f3 Mon Sep 17 00:00:00 2001 From: Niels Rotmensen Date: Mon, 6 Feb 2023 22:44:12 +0100 Subject: [PATCH 5/6] Add YouTube ad support to narrowcasting --- .../static/css/narrowcasting.css | 4 +- .../static/js/tv_narrowcasting.js | 128 ++++++++++++++++-- amelie/narrowcasting/templates/index.html | 5 + 3 files changed, 121 insertions(+), 16 deletions(-) diff --git a/amelie/narrowcasting/static/css/narrowcasting.css b/amelie/narrowcasting/static/css/narrowcasting.css index f0cadf6..920b28c 100644 --- a/amelie/narrowcasting/static/css/narrowcasting.css +++ b/amelie/narrowcasting/static/css/narrowcasting.css @@ -132,8 +132,7 @@ body { } .footer { - height: 137px; - padding: 15px 793px 15px 30px; + padding: 7.5px 793px 7.5px 20px; box-shadow: -25px 25px 215px 25px #222222; } @@ -143,7 +142,6 @@ body { .news-header { font-size:24px; - margin-bottom:5px; } .news-header span { diff --git a/amelie/narrowcasting/static/js/tv_narrowcasting.js b/amelie/narrowcasting/static/js/tv_narrowcasting.js index 7551a43..e950410 100644 --- a/amelie/narrowcasting/static/js/tv_narrowcasting.js +++ b/amelie/narrowcasting/static/js/tv_narrowcasting.js @@ -7,28 +7,53 @@ const SWAP_TIME = 20 * SECOND let date let time let activityTable +let companyAd +let news +let photo -window.addEventListener('load', () => { +// State +let companyBanners = [] +let selectedCompanyBanner = undefined +let activityPhotos = [] +let selectedActivity = undefined + +window.addEventListener('load', async () => { // Initializes the screen date = document.querySelector('#date') time = document.querySelector('#time') activityTable = document.querySelector('#activity-table') + adContainer = document.querySelector('.ads') + news = document.querySelector('.footer') + photo = document.querySelector('#photo') + photoName = document.querySelector('#photo-activity') // Update state - update(updateBannerState, 5*MINUTE) - update(updateActivityState, 5*MINUTE) + await update(updateBannerState, 5*MINUTE) + await update(updateActivityState, 5*MINUTE) + await update(updateNewsState, 5*MINUTE) + await update(updatePhotoState, 5*MINUTE) // Update UI update(updateDateTimeUI, SECOND) + update(updateBannerUI, 10*SECOND) + update(updatePhotoUI, 10*SECOND) }) /* Helper functions */ -const update = (callback, interval) => { - callback() - setInterval(callback, interval) +const update = async (callback, interval) => { + await callback() + setInterval(async () => await callback(), interval) +} + +const getYouTubeElement = (videoId) => { + return `` +} + +const getImgElement = (imageId) => { + return `` } /** @@ -64,18 +89,33 @@ const req = async (endpoint, params) => { /* Update state */ -const updateBannerState = () => { - req('getBanners', []) - .then(updateBannerUI) +const updateBannerState = async () => { + await req('getBanners', []) + .then(banners => { + console.log(banners) + companyBanners = banners + }) .catch(console.error) } -const updateActivityState = () => { - req('getUpcomingActivities', [4]) +const updateActivityState = async () => { + await req('getUpcomingActivities', [4, true]) .then(updateActivityUI) .catch(console.error) } +const updateNewsState = async () => { + await req('getNews', [2, true]) + .then(updateNewsUI) + .catch(console.error) +} + +const updatePhotoState = async () => { + await req('getLatestActivitiesWithPictures', [10]) + .then(activities => activityPhotos = activities) + .catch(console.error) +} + /* Update UI elements */ @@ -130,6 +170,68 @@ const updateActivityUI = (activities) => { } } -const updateBannerUI = (banners) => { - console.log(banners) +const updateNewsUI = (newsItems) => { + // Clear old news + news.textContent = '' + + const options = { + weekday: "short", month: "short", + day: "numeric" + } + + newsItems.forEach(item => { + const date = new Date(item["publicationDate"]).toLocaleString('nl-NL', options) + const elem = ` +
+ + ${date} + ${item.title} +
+
+ ${item.introduction} +
+ ` + const node = document.createElement("div") + node.classList.add("news-item") + node.innerHTML = elem + news.appendChild(node) + }) + +} + +const updateBannerUI = () => { + const currentIdx = companyBanners.indexOf(selectedCompanyBanner) + const nextBanner = companyBanners[(currentIdx + 1) % companyBanners.length] + + selectedCompanyBanner = nextBanner + + adContainer.innerHTML = '' + + if (nextBanner.type === 'image') { + adContainer.innerHTML = getImgElement(nextBanner.image) + } else if (nextBanner.type === 'video') { + adContainer.innerHTML = getYouTubeElement(nextBanner.videoId) + } +} + +const _setPhoto = (activity) => { + const imageIdx = 0 + // Select the large image, and if that does not exist we select the original one + let url = activity.images[imageIdx].large ?? activity.images[imageIdx].original + + photo.src = url + photoName.innerText = activity.title +} + +const updatePhotoUI = () => { + // First select an activity + const currentIdx = activityPhotos.indexOf(selectedActivity) + const nextActivity = activityPhotos[(currentIdx + 1) % activityPhotos.length] + + console.log('Selected activity: ', currentIdx) + + selectedActivity = nextActivity + + // Now, we can set the photo + _setPhoto(nextActivity) } \ No newline at end of file diff --git a/amelie/narrowcasting/templates/index.html b/amelie/narrowcasting/templates/index.html index bab5ae6..84ad48d 100644 --- a/amelie/narrowcasting/templates/index.html +++ b/amelie/narrowcasting/templates/index.html @@ -46,6 +46,11 @@ .glyphicon-globe:before { content: "\e135"; } + + .video { + height: 100%; + width: 100%; + } {% if theme == "christmas" %} From e53aa5d6525db052e44cef393e5f5183b0ecd274 Mon Sep 17 00:00:00 2001 From: Niels Rotmensen Date: Tue, 7 Feb 2023 14:55:05 +0100 Subject: [PATCH 6/6] narrowcasting: Add streamingIA support --- amelie/api/narrowcasting.py | 12 +++++++++++- .../0007_televisionvideo_video_type.py | 18 ++++++++++++++++++ amelie/companies/models.py | 9 +++++++++ .../static/js/tv_narrowcasting.js | 14 +++++++++++--- 4 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 amelie/companies/migrations/0007_televisionvideo_video_type.py diff --git a/amelie/api/narrowcasting.py b/amelie/api/narrowcasting.py index 5f873f2..c6fcaf1 100644 --- a/amelie/api/narrowcasting.py +++ b/amelie/api/narrowcasting.py @@ -9,7 +9,7 @@ from amelie.api.activitystream_utils import add_images_property, add_thumbnails_property from amelie.api.decorators import authentication_optional from amelie.api.utils import parse_datetime_parameter -from amelie.companies.models import TelevisionBanner +from amelie.companies.models import TelevisionBanner, TelevisionVideo from amelie.news.models import NewsItem from amelie.narrowcasting.models import TelevisionPromotion from amelie.room_duty.models import RoomDuty @@ -20,13 +20,23 @@ def get_narrowcasting_banners(request): result = [] banners = TelevisionBanner.objects.filter(start_date__lte=timezone.now(), end_date__gte=timezone.now(), active=True) + videos = TelevisionVideo.objects.filter(start_date__lte=timezone.now(), end_date__gte=timezone.now(), active=True) for banner in banners: result.append({ "name": banner.name, "image": "%s%s" % (settings.MEDIA_URL, str(banner.picture)), + "type": "image", "id": banner.id }) + + for video in videos: + result.append({ + "name": video.name, + "videoId": video.video_id, + "type": video.video_type, + "id": video.id + }) return result diff --git a/amelie/companies/migrations/0007_televisionvideo_video_type.py b/amelie/companies/migrations/0007_televisionvideo_video_type.py new file mode 100644 index 0000000..7bcb7a0 --- /dev/null +++ b/amelie/companies/migrations/0007_televisionvideo_video_type.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.16 on 2023-02-06 22:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('companies', '0006_televisionvideo'), + ] + + operations = [ + migrations.AddField( + model_name='televisionvideo', + name='video_type', + field=models.CharField(choices=[('youtube', 'YouTube'), ('streamingia', 'StreamingIA')], default='streamingia', max_length=11), + ), + ] diff --git a/amelie/companies/models.py b/amelie/companies/models.py index 3d6965a..76c798f 100644 --- a/amelie/companies/models.py +++ b/amelie/companies/models.py @@ -179,11 +179,19 @@ class Meta: class TelevisionVideo(models.Model): + class VideoTypes(models.TextChoices): + YOUTUBE = 'youtube', 'YouTube' + STREAMINGIA = 'streamingia', 'StreamingIA' + name = models.CharField(max_length=100) start_date = models.DateField() end_date = models.DateField() active = models.BooleanField(default=True) video_id = models.CharField(max_length=12) + video_type = models.CharField( + max_length=11, + choices=VideoTypes.choices, + default=VideoTypes.STREAMINGIA) class Meta: ordering = ['-start_date'] @@ -192,6 +200,7 @@ class Meta: class CompanyEvent(Event): + objects = EventManager() company = models.ForeignKey('Company', blank=True, null=True, on_delete=models.SET_NULL) company_text = models.CharField(max_length=100, blank=True) diff --git a/amelie/narrowcasting/static/js/tv_narrowcasting.js b/amelie/narrowcasting/static/js/tv_narrowcasting.js index e950410..1063287 100644 --- a/amelie/narrowcasting/static/js/tv_narrowcasting.js +++ b/amelie/narrowcasting/static/js/tv_narrowcasting.js @@ -35,8 +35,8 @@ window.addEventListener('load', async () => { // Update UI update(updateDateTimeUI, SECOND) - update(updateBannerUI, 10*SECOND) - update(updatePhotoUI, 10*SECOND) + update(updateBannerUI, 20*SECOND) + update(updatePhotoUI, 20*SECOND) }) /* @@ -52,6 +52,10 @@ const getYouTubeElement = (videoId) => { return `` } +const getStreamingElement = (videoId) => { + return `` +} + const getImgElement = (imageId) => { return `` } @@ -205,12 +209,16 @@ const updateBannerUI = () => { selectedCompanyBanner = nextBanner + console.log(nextBanner) + adContainer.innerHTML = '' if (nextBanner.type === 'image') { adContainer.innerHTML = getImgElement(nextBanner.image) - } else if (nextBanner.type === 'video') { + } else if (nextBanner.type === 'youtube') { adContainer.innerHTML = getYouTubeElement(nextBanner.videoId) + } else if (nextBanner.type === 'streamingia') { + adContainer.innerHTML = getStreamingElement(nextBanner.videoId) } }