diff --git a/src/controllers/api/channel/listController.js b/src/controllers/api/channel/listController.js index e55ea61..378df55 100644 --- a/src/controllers/api/channel/listController.js +++ b/src/controllers/api/channel/listController.js @@ -21,7 +21,6 @@ const {exceptionHandler, errorHandler} = require('../../../utils/loggerUtils'); //api account functions module.exports.get = async function(req, res){ try{ - //We pull this from the DB insteada activeChannels because we want to list channels that arent active after server restarts const chanGuide = await channelModel.getChannelList(); res.status(200); diff --git a/src/controllers/indexController.js b/src/controllers/indexController.js index 7c96e82..537656f 100644 --- a/src/controllers/indexController.js +++ b/src/controllers/indexController.js @@ -25,7 +25,8 @@ const {exceptionHandler, errorHandler} = require('../utils/loggerUtils'); //root index functions module.exports.get = async function(req, res){ try{ - return navRender.render(req, res, 'index', {unescape: validator.unescape}) + const chanGuide = await channelModel.getChannelList(); + return navRender.render(req, res, 'index', {chanGuide: chanGuide, unescape: validator.unescape}) }catch(err){ return exceptionHandler(res, err); } diff --git a/src/routers/api/channelRouter.js b/src/routers/api/channelRouter.js index 41290ed..0e1a1ed 100644 --- a/src/routers/api/channelRouter.js +++ b/src/routers/api/channelRouter.js @@ -66,7 +66,7 @@ router.post('/thumbnail', channelValidator.thumbnail(), thumbnailController.post router.get('/description', descriptionController.get); router.post('/description', channelValidator.description(), descriptionController.post); //list -router.get('/list', listController.get); +router.get('/list', channelModel.reqPermCheck("manageChannel"), listController.get); //settings router.get('/settings', channelModel.reqPermCheck("manageChannel"), settingsController.get); router.post('/settings', channelModel.reqPermCheck("changeSettings"), channelValidator.settingsMap(), settingsController.post); diff --git a/src/schemas/channel/channelSchema.js b/src/schemas/channel/channelSchema.js index 3976870..bd02fe0 100644 --- a/src/schemas/channel/channelSchema.js +++ b/src/schemas/channel/channelSchema.js @@ -284,10 +284,9 @@ channelSchema.statics.register = async function(channelObj, ownerObj){ /** * Generates Network-Friendly Browser-Digestable list of channels * @param {Boolean} includeHidden - Whether or not to include hidden channels within the list - * @param {Date} endDate - Limits amount of scheduled items sent, defaults to 90 minutes * @returns {Array} List of Network-Friendly Browser-Digestable Objects representing channels on the server */ -channelSchema.statics.getChannelList = async function(includeHidden = false, endDate = new Date(Date.now() + (1000 * 60 * 90))){ +channelSchema.statics.getChannelList = async function(includeHidden = false){ const chanDB = await this.find({}); var chanGuide = []; @@ -295,32 +294,12 @@ channelSchema.statics.getChannelList = async function(includeHidden = false, end chanDB.forEach((channel) => { //For each channel, push an object with only the information we need to the channel guide if(!channel.settings.hidden || includeHidden){ - //Create freeze-dried channel entry object (just add bong water) - const entry = { + chanGuide.push({ id: channel.id, name: channel.name, description: channel.description, - thumbnail: channel.thumbnail, - schedule: [] - } - - //If we're currently playing something - if(channel.media.nowPlaying != null){ - //Toss the currently playing item on - entry.schedule.push(channel.media.nowPlaying); - } - - //For every piece of scehduled media for this channel - for(const media of channel.media.scheduled){ - //If it starts before our cut-off - if(endDate.getTime() > media.startTime){ - //Add that shit, son! - entry.schedule.push(media); - } - } - - //add the channel entry to our return array - chanGuide.push(entry); + thumbnail: channel.thumbnail + }); } }); diff --git a/src/views/index.ejs b/src/views/index.ejs index 637b545..b2932b1 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -18,14 +18,25 @@ along with this program. If not, see . %> <%- include('partial/styles', {instance, user}); %> <%- include('partial/csrfToken', {csrfToken}); %> + <%= instance %> <%- include('partial/navbar', {user}); %> - <%- include('partial/channelGuide'); %> +

Start a new channel...

+
+ <% chanGuide.forEach((channel) => { %> +
+ +

<%= unescape(channel.name) %>

+ +

<%= unescape(channel.description) %> + +

+ <% }); %> +
diff --git a/src/views/partial/channelGuide.ejs b/src/views/partial/channelGuide.ejs deleted file mode 100644 index 2786c9c..0000000 --- a/src/views/partial/channelGuide.ejs +++ /dev/null @@ -1,31 +0,0 @@ -<%# Canopy - The next generation of stoner streaming software -Copyright (C) 2024-2026 Rainbownapkin and the TTN Community - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . %> - -
-
- -
-
-
-
-
-
-
-
-
-
\ No newline at end of file diff --git a/src/views/partial/navbar.ejs b/src/views/partial/navbar.ejs index 3c9a070..21fabcf 100644 --- a/src/views/partial/navbar.ejs +++ b/src/views/partial/navbar.ejs @@ -24,7 +24,7 @@ along with this program. If not, see . %> const linkObj = links[link]; if(!linkObj.registeredOnly || user != null){ %> - - <%= link %> + <%= link %> <% } } %>

diff --git a/www/css/channelGuide.css b/www/css/channelGuide.css deleted file mode 100644 index f30075c..0000000 --- a/www/css/channelGuide.css +++ /dev/null @@ -1,86 +0,0 @@ -/*Canopy - The next generation of stoner streaming software -Copyright (C) 2024-2026 Rainbownapkin and the TTN Community - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see .*/ - -.channel-guide-channel-list-column{ - width: 11em; - text-align: center; -} - -#channel-guide-div{ - display: flex; -} - -#channel-guide-schedule-container { - flex: 1; - width: calc(100% - 11em); -} - - -#channel-guide-schedule-index { - display: flex; - justify-content: space-between; -} - -.channel-guide-timeslot-label { - width: 100%; - text-align: center; - padding: 0; - margin: 0.3em 0; -} - -#channel-guide-new-channel-button h3{ - padding: 0.3em 0; - margin: 0; -} - -.channel-guide-schedule-row{ - height: 3.4em; -} - -.channel-guide-channel-list-entry { - position: relative; - display: flex; - justify-content: center; - align-items: center; - background-repeat: no-repeat; - background-size: cover; - background-position-y: center; -} - -.channel-guide-channel-list-entry h3 { - margin: 0; - z-index: 1; -} - -.channel-guide-schedule-track { - display: flex; - position: relative; -} - -.channel-guide-schedule-entry { - display: flex; - justify-content: center; - align-items: center; - overflow: hidden; - position: absolute; -} - -.channel-guide-schedule-entry h3 { - margin: 0; - text-overflow: ellipsis; - overflow: hidden; - text-wrap: nowrap; -} \ No newline at end of file diff --git a/www/css/index.css b/www/css/index.css new file mode 100644 index 0000000..6512046 --- /dev/null +++ b/www/css/index.css @@ -0,0 +1,66 @@ +/*Canopy - The next generation of stoner streaming software +Copyright (C) 2024-2026 Rainbownapkin and the TTN Community + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see .*/ + +@media (orientation: landscape){ + #channel-guide-div{ + max-width: 100vh; + } +} + +@media (orientation: portrait){ + #channel-guide-div{ + max-width: 80vw; + } +} + +#channel-guide-div{ + display: grid; + grid-template-columns: repeat(auto-fit, minmax(11em, 1fr)); + margin: 0 auto 0 auto; +} + +div.channel-guide-entry{ + overflow: hidden; + width: 10em; + margin: 0.5em; +} + +.channel-guide-entry{ + margin: 0.5em auto 0.5em auto; + padding: 0.2em; + display: flex; + flex-direction: column; +} + +.channel-guide-entry-item{ + margin: 0.1em auto 0.1em auto; +} + +.channel-guide-entry-item img{ + max-width: 7em; + max-height: 7em; +} + +span.channel-guide-entry-item{ + overflow-y: scroll; + height: 2em; + scrollbar-width: none; +} + +p.channel-guide-entry{ + font-size: 0.8em; + margin-left: 0; +} \ No newline at end of file diff --git a/www/css/panel/queue.css b/www/css/panel/queue.css index 8a8410c..d0e1597 100644 --- a/www/css/panel/queue.css +++ b/www/css/panel/queue.css @@ -112,8 +112,6 @@ div.queue-entry.live{ div.queue-entry p{ z-index: 2; pointer-events: none; - overflow: hidden; - text-overflow: ellipsis; } div.dragging-queue-entry{ diff --git a/www/css/theme/movie-night.css b/www/css/theme/movie-night.css index 4ad6774..2f7b2df 100644 --- a/www/css/theme/movie-night.css +++ b/www/css/theme/movie-night.css @@ -324,17 +324,27 @@ input.control-prompt, input.control-prompt:focus{ background-color: var(--bg1-alt0); } -/* chan guide */ -.channel-guide-channel-list-entry { - background-color: var(--bg2); +/* index */ +.channel-guide-entry{ + background-color: var(--bg1); + color: var(--accent1); } -.channel-guide-channel-list-entry::before { - content: ''; - background: var(--media-header-gradient); - position: absolute; - width: 100%; - height: 100%; +div.channel-guide-entry{ + border-radius: 0.3em; + box-shadow: 0.2em 0.2em 0.1em var(--bg1-alt0) inset; +} + +span.channel-guide-entry-item{ + background-color: var(--bg1-alt0); + box-shadow: 0.2em 0.2em 0.1em black inset; + border-radius: 0.3em; + margin: 0 0.1em 0 0.1em; +} + + +p.channel-guide-entry-item{ + background-color: var(--bg1-alt0); } /* channel settings */ diff --git a/www/css/theme/the-network-classic.css b/www/css/theme/the-network-classic.css index 2ab84d6..6a16288 100644 --- a/www/css/theme/the-network-classic.css +++ b/www/css/theme/the-network-classic.css @@ -364,47 +364,27 @@ input.control-prompt, input.control-prompt:focus{ display: none; } -/* chan guide */ -.channel-guide-channel-list-entry { - background-color: var(--bg2-alt1); -} - -.channel-guide-channel-list-entry::before { - content: ''; - background: var(--media-header-gradient); - position: absolute; - width: 100%; - height: 100%; -} - -#channel-guide-schedule-index { - border-bottom: 1px solid var(--accent0); -} -/*EWWIE WHY DOES CSS COUNTING START AT ONE?!?!*/ -.channel-guide-timeslot-label:not(:nth-child(1)){ - border-left: 1px solid var(--accent0); -} - -#channel-guide-new-channel-button h3{ - border-right: 1px solid var(--accent0); -} - -#channel-guide-new-channel-button{ - border-bottom: 1px solid var(--accent0); -} - -.channel-guide-schedule-entry{ - padding: 0 0.7em; +/* index */ +.channel-guide-entry{ background-color: var(--bg1); - border-left: 1px solid var(--accent0); + color: var(--accent1); } -.channel-guide-schedule-entry.starts-before{ - border-left: none; +div.channel-guide-entry{ + border-radius: 0.3em; + box-shadow: 0.2em 0.2em 0.1em var(--bg1-alt0) inset; } -.channel-guide-schedule-entry.last-ends-before{ - border-right: 1px solid var(--accent0); +span.channel-guide-entry-item{ + background-color: var(--bg1-alt0); + box-shadow: 0.2em 0.2em 0.1em black inset; + border-radius: 0.3em; + margin: 0 0.1em 0 0.1em; +} + + +p.channel-guide-entry-item{ + background-color: var(--bg1-alt0); } /* channel settings */ diff --git a/www/js/channelGuide.js b/www/js/channelGuide.js deleted file mode 100644 index cc9039e..0000000 --- a/www/js/channelGuide.js +++ /dev/null @@ -1,202 +0,0 @@ -/*Canopy - The next generation of stoner streaming software -Copyright (C) 2024-2026 Rainbownapkin and the TTN Community - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see .*/ - -class ChannelGuide{ - constructor(){ - //define property to hold refresh timer - this.refreshTimer; - - //define property to hold schedule cache - this.cache; - - //Grab DOM Nodes - this.scheduleIndex = document.querySelector('#channel-guide-schedule-index'); - this.channelList = document.querySelector('#channel-guide-channel-list'); - this.scheduledMediaDiv = document.querySelector('#channel-guide-scheduled-media'); - - //Refresh the schedule - this.refreshList(); - } - - async refreshList(now = new Date()){ - //Freshen up the local schedule cache - this.cache = await utils.ajax.getChannels(); - - //render the freshly pulled schedule data to the page - this.renderList(this.cache, now); - } - - renderList(schedule, now = new Date()){ - this.renderIndex(now); - this.renderChannelList(schedule); - this.renderSchedule(schedule, now); - - //if we've got a job to refresh the list - if(this.refreshTimer != null){ - //cancel it - clearTimeout(this.refreshTimer); - } - - //Set timer to refresh the thing erry minute on the minute - setTimeout(this.refreshList.bind(this), 1000 * (60 - now.getSeconds())); - } - - renderIndex(now = new Date()){ - //Clear schedule index - this.scheduleIndex.innerHTML = ''; - - //DO EM 3 TEIMS! - for(let slot = 0; slot < 3; slot++){ - //Clone passed date object to prevent over-writing - const setDate = new Date(now.getTime()); - - //Up timeslot by 30 minutes for each timeslot in the loop - setDate.setMinutes(setDate.getMinutes() + (slot * 30)); - - //Generate timeslot label - const timeslotLabel = document.createElement('h3'); - timeslotLabel.classList.add('channel-guide-timeslot-label'); - timeslotLabel.textContent = utils.ux.timeStringFromDate(setDate, false); - - //Append timeslot label to schedule index - this.scheduleIndex.appendChild(timeslotLabel); - } - } - - renderChannelList(schedule){ - //Clean out channel list - this.channelList.innerHTML = ''; - - for(const channel of schedule){ - //Create entry for channel - const entry = document.createElement('div'); - entry.classList.add('channel-guide-channel-list-entry', 'channel-guide-schedule-row'); - entry.style.backgroundImage = `Url("${channel.thumbnail}")`; - - //Throw together a channel label - const entryLabel = document.createElement('h3'); - entryLabel.textContent = utils.unescapeEntities(channel.name); - - //Some assembly required - entry.appendChild(entryLabel); - - //Add channel entry to the list - this.channelList.appendChild(entry); - } - } - - renderSchedule(schedule, now = new Date()){ - //Clear out the schedule - this.scheduledMediaDiv.innerHTML = ''; - - schedule = this.fillDeadAir(schedule, now); - - //Iterate through all of the received channels - for(const channel of schedule){ - - //Create a div to hold all of the current channels entries - const channelTrack = document.createElement('div'); - channelTrack.classList.add('channel-guide-schedule-track', 'channel-guide-schedule-row'); - - //For every scheduled video - for(const entryIndex in channel.schedule){ - //Get entry by index - const entry = channel.schedule[entryIndex]; - //Check if this is the last item - const last = entryIndex == channel.schedule.length - 1; - - //slot entry into schedule track - channelTrack.appendChild(this.renderScheduleItem(entry, now, last)); - } - - //Inject channel track into schedule div - this.scheduledMediaDiv.appendChild(channelTrack); - } - } - - - fillDeadAir(schedule){ - //For each video in the schedule - for(const video of schedule){ - - } - - //Return processed schedule - return schedule; - } - - renderScheduleItem(item, now = new Date(), last = false){ - //Create div for current schedule entry - const entryDiv = document.createElement('div'); - entryDiv.classList.add('channel-guide-schedule-entry', 'channel-guide-schedule-row'); - - //Throw down an entry label - const entryLabel = document.createElement('h3'); - entryLabel.textContent = utils.unescapeEntities(item.title); - - //calculate end epoch - const end = item.startTime + (item.duration * 1000); - - //Place entryDiv by scheduled time - entryDiv.style.left = `${this.offsetByEpoch(item.startTime, now)}px`; - entryDiv.style.right = `${this.offsetByEpoch(end, now, true)}px`; - - //if this started before the schedule time range - if(item.startTime < now.getTime()){ - //mark it for css - entryDiv.classList.add('starts-before'); - } - - //if this is the last item and it ends before the schedule time range - if(last && end < now.getTime() + 5400000){ - //mark it for css - entryDiv.classList.add('last-ends-before'); - } - - //Assemble the entry - entryDiv.appendChild(entryLabel); - - //Return assembled entry div - return entryDiv; - } - - offsetByEpoch(scheduleEpoch, now = new Date(), rightOffset = false){ - //Calculate how many milliseconds after the current time our scheduled item is - const ms = scheduleEpoch - now.getTime(); - - //Calculate and return offset by milliseconds - return this.offsetByMilliseconds(ms, rightOffset, rightOffset); - } - - offsetByMilliseconds(ms, rightOffset = false){ - //Calculate place in schedule as float between 0 and 1 by deviding input by schedules range (90 minutes) - //Making sure to reverse value if rightOffset is thrown - const timeFloat = rightOffset ? 1 - ms / 5400000 : ms / 5400000; - - //Get maximum position relative to the range itself - const offsetMax = this.scheduledMediaDiv.getBoundingClientRect().width; - - //Calculate offset by timefloat and offset max - const offset = (offsetMax * timeFloat) < 0 ? 0 : (offsetMax * timeFloat); - - //return position relative to parent, adding channel list width to left side to handle offset - return offset; - - } - -} - -const channelGuide = new ChannelGuide(); \ No newline at end of file diff --git a/www/js/utils.js b/www/js/utils.js index 1d042a8..4661e3a 100644 --- a/www/js/utils.js +++ b/www/js/utils.js @@ -1012,18 +1012,6 @@ class canopyAjaxUtils{ } //Channel - async getChannels(){ - var response = await fetch(`/api/channel/list`,{ - method: "GET" - }); - - if(response.ok){ - return await response.json(); - }else{ - utils.ux.displayResponseError(await response.json()); - } - } - async newChannel(name, description, thumbnail, verification){ var response = await fetch(`/api/channel/register`,{ method: "POST",