Continued work on media scheduler
This commit is contained in:
parent
9d01b4c962
commit
d5a2a51be2
|
|
@ -44,16 +44,38 @@ module.exports = class{
|
||||||
|
|
||||||
defineListeners(socket){
|
defineListeners(socket){
|
||||||
socket.on("queue", (data) => {this.queueURL(socket, data)});
|
socket.on("queue", (data) => {this.queueURL(socket, data)});
|
||||||
|
socket.on("delete", (data => {this.deleteMedia(socket, data)}));
|
||||||
|
socket.on("move", (data => {this.moveMedia(socket, data)}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async queueURL(socket, data){
|
async queueURL(socket, data){
|
||||||
try{
|
try{
|
||||||
//pull URL and start time from data
|
//pull URL and start time from data
|
||||||
let {url, start} = data;
|
let {url, start, title} = data;
|
||||||
|
|
||||||
//Pull media list
|
//Pull media list
|
||||||
const mediaList = await yanker.yankMedia(url);
|
const mediaList = await yanker.yankMedia(url, title);
|
||||||
|
|
||||||
|
//If we didn't find any media
|
||||||
|
if(mediaList == null || mediaList.length <= 0){
|
||||||
|
//Bitch, moan, complain...
|
||||||
|
loggerUtils.socketErrorHandler(socket, "No media found!", "queue");
|
||||||
|
//and ignore it!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we have an invalid time
|
||||||
|
if(start == null || start < (new Date).getTime()){
|
||||||
|
//Get last item from schedule
|
||||||
|
const lastItem = (Array.from(this.schedule)[this.schedule.size - 1]);
|
||||||
|
|
||||||
|
//if we have a last item
|
||||||
|
if(lastItem != null){
|
||||||
|
//Throw it on five ms after the last item
|
||||||
|
start = lastItem[1].startTime + (lastItem[1].duration * 1000) + 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Queue the first media object given
|
//Queue the first media object given
|
||||||
this.queueMedia(mediaList[0], start, socket);
|
this.queueMedia(mediaList[0], start, socket);
|
||||||
|
|
@ -62,6 +84,24 @@ module.exports = class{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteMedia(socket, data){
|
||||||
|
try{
|
||||||
|
//Remove media by UUID
|
||||||
|
this.removeMedia(data.uuid, socket);
|
||||||
|
}catch(err){
|
||||||
|
return loggerUtils.socketExceptionHandler(socket, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moveMedia(socket, data){
|
||||||
|
try{
|
||||||
|
//Move media by UUID
|
||||||
|
this.rescheduleMedia(data.uuid, data.start, socket);
|
||||||
|
}catch(err){
|
||||||
|
return loggerUtils.socketExceptionHandler(socket, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Default start time to now + half a second to give everyone time to process shit
|
//Default start time to now + half a second to give everyone time to process shit
|
||||||
queueMedia(inputMedia, start = new Date().getTime() + 50, socket){
|
queueMedia(inputMedia, start = new Date().getTime() + 50, socket){
|
||||||
//Create a new media queued object, set start time to now
|
//Create a new media queued object, set start time to now
|
||||||
|
|
@ -69,9 +109,6 @@ module.exports = class{
|
||||||
|
|
||||||
//schedule the media
|
//schedule the media
|
||||||
this.scheduleMedia(mediaObj, socket);
|
this.scheduleMedia(mediaObj, socket);
|
||||||
|
|
||||||
//Refresh the next timer to ensure whatever comes on next is right
|
|
||||||
this.refreshNextTimer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshNextTimer(){
|
refreshNextTimer(){
|
||||||
|
|
@ -93,6 +130,62 @@ module.exports = class{
|
||||||
this.nextTimer = setTimeout(()=>{this.start(nextItem)}, startsIn);
|
this.nextTimer = setTimeout(()=>{this.start(nextItem)}, startsIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rescheduleMedia(uuid, start = new Date().getTime() + 50, socket){
|
||||||
|
//Find and remove media from the schedule by UUID
|
||||||
|
const media = this.removeMedia(uuid);
|
||||||
|
|
||||||
|
//If we got a bad request
|
||||||
|
if(media == null){
|
||||||
|
//If an originating socket was provided for this request
|
||||||
|
if(socket != null){
|
||||||
|
//Yell at the user for being an asshole
|
||||||
|
loggerUtils.socketErrorHandler(socket, "Cannot move non-existant item!", "queue");
|
||||||
|
}
|
||||||
|
//Ignore it
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set media time
|
||||||
|
media.startTime = start;
|
||||||
|
|
||||||
|
//Re-schedule the media for the given time
|
||||||
|
this.scheduleMedia(media, socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMedia(uuid, socket){
|
||||||
|
//Get requested media
|
||||||
|
const media = this.getItemByUUID(uuid);
|
||||||
|
|
||||||
|
//If we got a bad request
|
||||||
|
if(media == null){
|
||||||
|
//If an originating socket was provided for this request
|
||||||
|
if(socket != null){
|
||||||
|
//Yell at the user for being an asshole
|
||||||
|
loggerUtils.socketErrorHandler(socket, "Cannot delete non-existant item!", "queue");
|
||||||
|
}
|
||||||
|
//Ignore it
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we're currently playing the requested item.
|
||||||
|
if(this.nowPlaying != null && this.nowPlaying.uuid == uuid){
|
||||||
|
//End playback
|
||||||
|
this.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Take the item out of the schedule
|
||||||
|
this.schedule.delete(media.startTime);
|
||||||
|
|
||||||
|
//Refresh next timer
|
||||||
|
this.refreshNextTimer();
|
||||||
|
|
||||||
|
//Broadcast the channel queue
|
||||||
|
this.broadcastQueue();
|
||||||
|
|
||||||
|
//return found media in-case our calling function needs it :P
|
||||||
|
return media;
|
||||||
|
}
|
||||||
|
|
||||||
scheduleMedia(mediaObj, socket){
|
scheduleMedia(mediaObj, socket){
|
||||||
/* This is a fun method and I think it deserves it's own little explination...
|
/* This is a fun method and I think it deserves it's own little explination...
|
||||||
Since we're working with a time based schedule, using start epochs as keys for our iterable seemed the best option
|
Since we're working with a time based schedule, using start epochs as keys for our iterable seemed the best option
|
||||||
|
|
@ -159,6 +252,9 @@ module.exports = class{
|
||||||
|
|
||||||
//Broadcast the channel queue
|
//Broadcast the channel queue
|
||||||
this.broadcastQueue();
|
this.broadcastQueue();
|
||||||
|
|
||||||
|
//Refresh the next timer to ensure whatever comes on next is right
|
||||||
|
this.refreshNextTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
start(mediaObj){
|
start(mediaObj){
|
||||||
|
|
@ -270,6 +366,17 @@ module.exports = class{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getItemByUUID(uuid){
|
||||||
|
//Iterate through the schedule
|
||||||
|
for(let item of this.schedule){
|
||||||
|
//If the uuid matches
|
||||||
|
if(item[1].uuid == uuid){
|
||||||
|
//return the found item
|
||||||
|
return item[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sendMedia(socket){
|
sendMedia(socket){
|
||||||
//Create data object
|
//Create data object
|
||||||
const data = {
|
const data = {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ const validator = require('validator');//No express here, so regular validator i
|
||||||
const iaUtil = require('./internetArchiveUtils');
|
const iaUtil = require('./internetArchiveUtils');
|
||||||
const media = require('../../app/channel/media/media');
|
const media = require('../../app/channel/media/media');
|
||||||
|
|
||||||
module.exports.yankMedia = async function(url){
|
module.exports.yankMedia = async function(url, title){
|
||||||
const pullType = await this.getMediaType(url);
|
const pullType = await this.getMediaType(url);
|
||||||
|
|
||||||
if(pullType == 'ia'){
|
if(pullType == 'ia'){
|
||||||
|
|
@ -34,13 +34,21 @@ module.exports.yankMedia = async function(url){
|
||||||
for(let file of mediaInfo.files){
|
for(let file of mediaInfo.files){
|
||||||
//Split file path by directories
|
//Split file path by directories
|
||||||
const path = file.name.split('/');
|
const path = file.name.split('/');
|
||||||
|
|
||||||
//pull filename from path
|
//pull filename from path
|
||||||
const name = path[path.length - 1];
|
const name = path[path.length - 1];
|
||||||
|
|
||||||
//Construct link from pulled info
|
//Construct link from pulled info
|
||||||
const link = `https://archive.org/download/${mediaInfo.metadata.identifier}/${file.name}`;
|
const link = `https://archive.org/download/${mediaInfo.metadata.identifier}/${file.name}`;
|
||||||
|
|
||||||
//Create new media object from file info
|
//if we where handed a null title
|
||||||
mediaList.push(new media(name, name, link, link, 'ia', Number(file.length)));
|
if(title == null || title == ''){
|
||||||
|
//Create new media object from file info substituting filename for title
|
||||||
|
mediaList.push(new media(name, name, link, link, 'ia', Number(file.length)));
|
||||||
|
}else{
|
||||||
|
//Create new media object from file info
|
||||||
|
mediaList.push(new media(title, name, link, link, 'ia', Number(file.length)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//return media object list
|
//return media object list
|
||||||
|
|
@ -52,12 +60,16 @@ module.exports.yankMedia = async function(url){
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.getMediaType = async function(url){
|
module.exports.getMediaType = async function(url){
|
||||||
|
//Encode URI in-case we where handed something a little too humie friendly
|
||||||
|
url = encodeURI(url);
|
||||||
|
|
||||||
//Check if we have a valid url
|
//Check if we have a valid url
|
||||||
if(!validator.isURL(url)){
|
if(!validator.isURL(url)){
|
||||||
//If not toss the fucker out
|
//If not toss the fucker out
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//If we have link to a resource from archive.org
|
//If we have link to a resource from archive.org
|
||||||
if(url.match(/^https\:\/\/archive.org\//g)){
|
if(url.match(/^https\:\/\/archive.org\//g)){
|
||||||
//return internet archive code
|
//return internet archive code
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
||||||
<link rel="stylesheet" type="text/css" href="/css/panel/queue.css">
|
<link rel="stylesheet" type="text/css" href="/css/panel/queue.css">
|
||||||
<div id="queue-panel-layout-controller">
|
<div id="queue-panel-layout-controller">
|
||||||
<div id="queue-controls">
|
<div id="queue-controls">
|
||||||
<span id="queue-media-prompts">
|
<div id="queue-control-buttons">
|
||||||
|
<button id="queue-add-media"><i class="bi-plus-lg"></i></button>
|
||||||
|
<button id="queue-search-media"><i class="bi-search"></i></button>
|
||||||
|
<button id="queue-playlists"><i class="bi-list"></i></button>
|
||||||
|
<button id="queue-scroll-lock" class="positive-button"><i class="bi-clock-fill"></i></button>
|
||||||
|
<button id="queue-export-clear"><i class="bi-trash-fill"></i></button>
|
||||||
|
<button id="queue-export-date"><i class="bi-calendar-fill"></i></button>
|
||||||
|
<button id="queue-export-lock" class="positive-button"><i class="bi-unlock-fill"></i></button>
|
||||||
|
</div>
|
||||||
|
<div id="queue-media-prompts" style="display: none;">
|
||||||
<div class="panel-control-prompt control-prompt">
|
<div class="panel-control-prompt control-prompt">
|
||||||
<input placeholder="Media Link..." id="media-link-input" class="control-prompt">
|
<input placeholder="Media Link..." id="media-link-input" class="control-prompt">
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -24,9 +33,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
||||||
<%# Probably not the cleanest way to do this but fuggit %>
|
<%# Probably not the cleanest way to do this but fuggit %>
|
||||||
<input placeholder="Media Name..." id="media-name-input" class="control-prompt">
|
<input placeholder="Media Name..." id="media-name-input" class="control-prompt">
|
||||||
<button id="queue-last-button" class="positive-button">Queue Last</button>
|
<button id="queue-last-button" class="positive-button">Queue Last</button>
|
||||||
|
<button id="queue-at-button" class="positive-button">Queue At...</button>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="queue-control-offset"></div>
|
||||||
<div id="queue">
|
<div id="queue">
|
||||||
<div id="queue-marker-container"></div>
|
<div id="queue-marker-container"></div>
|
||||||
<div id="queue-container"></div>
|
<div id="queue-container"></div>
|
||||||
|
|
|
||||||
21
src/views/partial/popup/scheduleMedia.ejs
Normal file
21
src/views/partial/popup/scheduleMedia.ejs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<%# Canopy - The next generation of stoner streaming software
|
||||||
|
Copyright (C) 2024-2025 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 <https://www.gnu.org/licenses/>. %>
|
||||||
|
<%# <link rel="stylesheet" type="text/css" href="/css/popup/schedule.css"> %>
|
||||||
|
<h3 class="popup-title">Schedule Media</h3>
|
||||||
|
<div>
|
||||||
|
<input type="datetime-local" id="schedule-media-popup-time-prompt">
|
||||||
|
<button class="positive-button" id="schedule-media-popup-schedule-button">Schedule</button>
|
||||||
|
</div>
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<%# Canopy - The next generation of stoner streaming software
|
<!-- Canopy - The next generation of stoner streaming software
|
||||||
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
|
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -12,7 +12,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU Affero General Public License for more details.
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
along with this program. If not, see <https://www.gnu.org/licenses/>. -->
|
||||||
<%# Technically favicon has nothing to do with .css, but it's still looks related, uses a link tag, and globally used :P %>
|
<%# Technically favicon has nothing to do with .css, but it's still looks related, uses a link tag, and globally used :P %>
|
||||||
<link rel="icon" type="image/x-icon" href="/img/sweet_leaf_simple.png">
|
<link rel="icon" type="image/x-icon" href="/img/sweet_leaf_simple.png">
|
||||||
<link rel="stylesheet" href="/lib/bootstrap-icons/font/bootstrap-icons.css">
|
<link rel="stylesheet" href="/lib/bootstrap-icons/font/bootstrap-icons.css">
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,7 @@ span.user-entry{
|
||||||
#cpanel-pinned-div{
|
#cpanel-pinned-div{
|
||||||
position: relative;
|
position: relative;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
|
flex: 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cpanel-div{
|
.cpanel-div{
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,29 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#queue-controls{
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#queue-control-offset{
|
||||||
|
margin-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#queue-control-buttons{
|
||||||
|
height: 2em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#queue-control-buttons button{
|
||||||
|
width: 4em;
|
||||||
|
}
|
||||||
|
|
||||||
#queue{
|
#queue{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
@ -58,6 +81,7 @@ div.queue-entry{
|
||||||
margin: 0 1em;
|
margin: 0 1em;
|
||||||
right: 0;
|
right: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
cursor:grab;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.queue-entry a{
|
div.queue-entry a{
|
||||||
|
|
@ -79,4 +103,8 @@ div.queue-entry a{
|
||||||
right: 0;
|
right: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 3em;
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-tooltip p{
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
@ -460,6 +460,10 @@ span.emote-list-trash-icon{
|
||||||
border: 1px solid var(--accent0)
|
border: 1px solid var(--accent0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#queue-controls{
|
||||||
|
background-color: var(--bg0-alpha1);
|
||||||
|
}
|
||||||
|
|
||||||
span.queue-marker{
|
span.queue-marker{
|
||||||
background-color: var(--accent0);
|
background-color: var(--accent0);
|
||||||
}
|
}
|
||||||
|
|
@ -477,8 +481,14 @@ div.queue-entry{
|
||||||
border: 1px solid var(--accent1);
|
border: 1px solid var(--accent1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.queue-entry a{
|
.queue-entry p{
|
||||||
color: var(--accent1);
|
color: var(--accent1);
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-tooltip p{
|
||||||
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* altcha theming*/
|
/* altcha theming*/
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ class channel{
|
||||||
this.socket.on("error", console.log);
|
this.socket.on("error", console.log);
|
||||||
|
|
||||||
this.socket.on("queue", (data) => {
|
this.socket.on("queue", (data) => {
|
||||||
this.queue = data.queue;
|
this.queue = new Map(data.queue);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ class channel{
|
||||||
this.chatBox.handleClientInfo(data);
|
this.chatBox.handleClientInfo(data);
|
||||||
|
|
||||||
//Store queue for use by the queue panel
|
//Store queue for use by the queue panel
|
||||||
this.queue = data.queue;
|
this.queue = new Map(data.queue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ class chatBox{
|
||||||
this.autocompleteDisplay.addEventListener("click", this.tabComplete.bind(this));
|
this.autocompleteDisplay.addEventListener("click", this.tabComplete.bind(this));
|
||||||
this.sendButton.addEventListener("click", this.send.bind(this));
|
this.sendButton.addEventListener("click", this.send.bind(this));
|
||||||
this.settingsIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new panelObj(client))});
|
this.settingsIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new panelObj(client))});
|
||||||
this.adminIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new panelObj(client))});
|
this.adminIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new queuePanel(client))});
|
||||||
this.emoteIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new emotePanel(client))});
|
this.emoteIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new emotePanel(client))});
|
||||||
|
|
||||||
//Header icons
|
//Header icons
|
||||||
|
|
@ -310,11 +310,11 @@ class chatBox{
|
||||||
const limit = window.innerWidth * .2;
|
const limit = window.innerWidth * .2;
|
||||||
|
|
||||||
//Set width to target or 20vh depending on whether or not we've hit the width limit
|
//Set width to target or 20vh depending on whether or not we've hit the width limit
|
||||||
this.chatPanel.style.width = targetChatWidth > limit ? `${targetChatWidth}px` : '20vh';
|
this.chatPanel.style.flexBasis = targetChatWidth > limit ? `${targetChatWidth}px` : '20vh';
|
||||||
|
|
||||||
//Fix busted layout
|
//Fix busted layout
|
||||||
var pageBreak = document.body.scrollWidth - document.body.getBoundingClientRect().width;
|
var pageBreak = document.body.scrollWidth - document.body.getBoundingClientRect().width;
|
||||||
this.chatPanel.style.width = `${this.chatPanel.getBoundingClientRect().width + pageBreak}px`;
|
this.chatPanel.style.flexBasis = `${this.chatPanel.getBoundingClientRect().width + pageBreak}px`;
|
||||||
//This sometimes gets called before userList ahs been initiated :p
|
//This sometimes gets called before userList ahs been initiated :p
|
||||||
if(this.client.userList != null){
|
if(this.client.userList != null){
|
||||||
this.client.userList.clickDragger.fixCutoff();
|
this.client.userList.clickDragger.fixCutoff();
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ class cPanel{
|
||||||
this.poppedPanels = [];
|
this.poppedPanels = [];
|
||||||
|
|
||||||
//ClickDragger Objects
|
//ClickDragger Objects
|
||||||
this.activePanelDragger = new canopyUXUtils.clickDragger("#cpanel-active-drag-handle", "#cpanel-active-div", false);
|
this.activePanelDragger = new canopyUXUtils.clickDragger("#cpanel-active-drag-handle", "#cpanel-active-div", false, null, false);
|
||||||
this.pinnedPanelDragger = new canopyUXUtils.clickDragger("#cpanel-pinned-drag-handle", "#cpanel-pinned-div", false, this.client.chatBox.clickDragger);
|
this.pinnedPanelDragger = new canopyUXUtils.clickDragger("#cpanel-pinned-drag-handle", "#cpanel-pinned-div", false, this.client.chatBox.clickDragger);
|
||||||
|
|
||||||
//Element Nodes
|
//Element Nodes
|
||||||
|
|
@ -150,6 +150,7 @@ class panelObj{
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.pageURL = pageURL;
|
this.pageURL = pageURL;
|
||||||
this.panelDocument = panelDocument;
|
this.panelDocument = panelDocument;
|
||||||
|
this.ownerDoc = this.panelDocument.ownerDocument == null ? this.panelDocument : this.panelDocument.ownerDocument;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,6 +163,8 @@ class panelObj{
|
||||||
}
|
}
|
||||||
|
|
||||||
docSwitch(){
|
docSwitch(){
|
||||||
|
//Set owner doc
|
||||||
|
this.ownerDoc = this.panelDocument.ownerDocument == null ? this.panelDocument : this.panelDocument.ownerDocument;
|
||||||
}
|
}
|
||||||
|
|
||||||
closer(){
|
closer(){
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,16 @@ class queuePanel extends panelObj{
|
||||||
//Create variable to hold rescale timer
|
//Create variable to hold rescale timer
|
||||||
this.rescaleTimer = null;
|
this.rescaleTimer = null;
|
||||||
|
|
||||||
|
this.autoscroll = true;
|
||||||
|
|
||||||
//Define non-input event listeners
|
//Define non-input event listeners
|
||||||
this.defineListeners();
|
this.defineListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
docSwitch(){
|
docSwitch(){
|
||||||
|
//Call derived doc switch function
|
||||||
|
super.docSwitch();
|
||||||
|
|
||||||
//Get queue div
|
//Get queue div
|
||||||
this.queue = this.panelDocument.querySelector('#queue');
|
this.queue = this.panelDocument.querySelector('#queue');
|
||||||
//Get queue container
|
//Get queue container
|
||||||
|
|
@ -24,6 +29,21 @@ class queuePanel extends panelObj{
|
||||||
//Re-acquire time marker
|
//Re-acquire time marker
|
||||||
this.timeMarker = this.panelDocument.querySelector('#time-marker');
|
this.timeMarker = this.panelDocument.querySelector('#time-marker');
|
||||||
|
|
||||||
|
//Get main control buttons
|
||||||
|
this.addMediaButton = this.panelDocument.querySelector('#queue-add-media');
|
||||||
|
this.scrollLockButton = this.panelDocument.querySelector('#queue-scroll-lock');
|
||||||
|
|
||||||
|
//Get control divs
|
||||||
|
//Add Media
|
||||||
|
this.addMediaDiv = this.panelDocument.querySelector('#queue-media-prompts');
|
||||||
|
|
||||||
|
//Get control div elements
|
||||||
|
//Add Media
|
||||||
|
this.addMediaLinkPrompt = this.panelDocument.querySelector('#media-link-input');
|
||||||
|
this.addMediaNamePrompt = this.panelDocument.querySelector('#media-name-input');
|
||||||
|
this.queueLastButton = this.panelDocument.querySelector('#queue-last-button');
|
||||||
|
this.queueAtButton = this.panelDocument.querySelector('#queue-at-button');
|
||||||
|
|
||||||
//Render out the queue
|
//Render out the queue
|
||||||
this.fullRender();
|
this.fullRender();
|
||||||
|
|
||||||
|
|
@ -38,13 +58,48 @@ class queuePanel extends panelObj{
|
||||||
|
|
||||||
|
|
||||||
defineListeners(){
|
defineListeners(){
|
||||||
this.client.socket.on("queue", (data) => {
|
//Render queue when we receive a new copy of the queue data from the server
|
||||||
this.renderQueue();
|
this.client.socket.on("clientMetadata", (data) => {this.renderQueue();})
|
||||||
})
|
this.client.socket.on("queue", (data) => {this.renderQueue();})
|
||||||
}
|
}
|
||||||
|
|
||||||
setupInput(){
|
setupInput(){
|
||||||
|
//Re-render queue and time-marker on window resize as time-relative absolute positioning will be absolutely thrown
|
||||||
|
this.ownerDoc.defaultView.addEventListener('resize', this.resizeRender.bind(this));
|
||||||
|
|
||||||
|
//queue
|
||||||
this.queue.addEventListener('wheel', this.scaleScroll.bind(this));
|
this.queue.addEventListener('wheel', this.scaleScroll.bind(this));
|
||||||
|
|
||||||
|
//control bar controls
|
||||||
|
this.addMediaButton.addEventListener('click', this.toggleAddMedia.bind(this));
|
||||||
|
this.scrollLockButton.addEventListener('click', this.lockScroll.bind(this));
|
||||||
|
|
||||||
|
//control bar divs
|
||||||
|
this.queueLastButton.addEventListener('click', this.queueLast.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAddMedia(event){
|
||||||
|
if(this.addMediaDiv.style.display == 'none'){
|
||||||
|
this.addMediaDiv.style.display = '';
|
||||||
|
}else{
|
||||||
|
this.addMediaDiv.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queueLast(event){
|
||||||
|
//Send off the request
|
||||||
|
this.client.socket.emit("queue",{url:this.addMediaLinkPrompt.value, title:this.addMediaNamePrompt.value});
|
||||||
|
this.addMediaLinkPrompt.value = '';
|
||||||
|
this.addMediaNamePrompt.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
lockScroll(event){
|
||||||
|
//Enable scroll lock
|
||||||
|
this.autoscroll = true;
|
||||||
|
//Light the indicator
|
||||||
|
this.scrollLockButton.classList.add('positive-button');
|
||||||
|
//Render the marker early to insta-jump
|
||||||
|
this.renderTimeMarker();
|
||||||
}
|
}
|
||||||
|
|
||||||
scaleScroll(event){
|
scaleScroll(event){
|
||||||
|
|
@ -85,7 +140,6 @@ class queuePanel extends panelObj{
|
||||||
//Clear out the queue UI
|
//Clear out the queue UI
|
||||||
this.clearQueue();
|
this.clearQueue();
|
||||||
|
|
||||||
|
|
||||||
//Calculate new scale
|
//Calculate new scale
|
||||||
const newScale = this.scale + (scaleFactor * scrollDirection);
|
const newScale = this.scale + (scaleFactor * scrollDirection);
|
||||||
|
|
||||||
|
|
@ -107,6 +161,12 @@ class queuePanel extends panelObj{
|
||||||
clearTimeout(this.rescaleTimer);
|
clearTimeout(this.rescaleTimer);
|
||||||
//Set timeout to re-render after input stops
|
//Set timeout to re-render after input stops
|
||||||
this.rescaleTimer = setTimeout(this.fullRender.bind(this), 500);
|
this.rescaleTimer = setTimeout(this.fullRender.bind(this), 500);
|
||||||
|
//Otherwise, if we're just scrolling
|
||||||
|
}else{
|
||||||
|
//Disable scroll lock
|
||||||
|
this.autoscroll = false;
|
||||||
|
//Unlight the indicator
|
||||||
|
this.scrollLockButton.classList.remove('positive-button');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,12 +227,26 @@ class queuePanel extends panelObj{
|
||||||
return timeStrings.join(', ');
|
return timeStrings.join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resizeRender(event){
|
||||||
|
const date = new Date();
|
||||||
|
this.renderQueue(date);
|
||||||
|
this.renderTimeMarker(date);
|
||||||
|
}
|
||||||
|
|
||||||
clearQueue(){
|
clearQueue(){
|
||||||
//Clear out queue container
|
//Clear out queue container
|
||||||
this.queueContainer.innerHTML = '';
|
this.queueContainer.innerHTML = '';;
|
||||||
//Clear out queue marker container
|
//Clear out queue marker container
|
||||||
this.queueMarkerContainer.innerHTML = '';
|
this.queueMarkerContainer.innerHTML = '';
|
||||||
|
|
||||||
|
//Grab all related tooltips
|
||||||
|
const tooltips = this.ownerDoc.body.querySelectorAll('.media-tooltip');
|
||||||
|
|
||||||
|
//clear them out since we're clearing out the elements with the event to remove them
|
||||||
|
//These should clear out on their own on mousemove but this makes things look a *little* prettier :)
|
||||||
|
for(let tooltip of tooltips){
|
||||||
|
tooltip.parentNode.remove();
|
||||||
|
}
|
||||||
|
|
||||||
//Clear any marker timers
|
//Clear any marker timers
|
||||||
clearTimeout(this.timeMarkerTimer);
|
clearTimeout(this.timeMarkerTimer);
|
||||||
|
|
@ -206,13 +280,16 @@ class queuePanel extends panelObj{
|
||||||
}
|
}
|
||||||
|
|
||||||
//render the time marker
|
//render the time marker
|
||||||
this.renderTimeMarker(date);
|
this.renderTimeMarker(date, true);
|
||||||
|
|
||||||
//render out the queue
|
//render out the queue
|
||||||
this.renderQueue(date);
|
this.renderQueue(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderQueue(date = new Date()){
|
renderQueue(date = new Date()){
|
||||||
|
//Clear out queue container
|
||||||
|
this.queueContainer.innerHTML = '';
|
||||||
|
|
||||||
//for every entry in the queue
|
//for every entry in the queue
|
||||||
for(let entry of this.client.queue){
|
for(let entry of this.client.queue){
|
||||||
//Create entry div
|
//Create entry div
|
||||||
|
|
@ -224,9 +301,64 @@ class queuePanel extends panelObj{
|
||||||
entryDiv.style.bottom = `${this.offsetByDate(new Date(entry[1].startTime + (entry[1].duration * 1000)), true)}px`;
|
entryDiv.style.bottom = `${this.offsetByDate(new Date(entry[1].startTime + (entry[1].duration * 1000)), true)}px`;
|
||||||
|
|
||||||
//Create entry title
|
//Create entry title
|
||||||
const entryTitle = document.createElement('a');
|
const entryTitle = document.createElement('p');
|
||||||
entryTitle.textContent = entry[1].title;
|
entryTitle.textContent = entry[1].title;
|
||||||
entryTitle.href = entry[1].url;
|
|
||||||
|
//Tooltip generation
|
||||||
|
//tooltip div
|
||||||
|
const tooltipDiv = document.createElement('div');
|
||||||
|
tooltipDiv.classList.add('media-tooltip');
|
||||||
|
|
||||||
|
//tooltip title
|
||||||
|
const tooltipTitle = document.createElement('p');
|
||||||
|
tooltipTitle.textContent = `Title: ${entry[1].title}`;
|
||||||
|
|
||||||
|
//tooltip filename
|
||||||
|
const tooltipFilename = document.createElement('p');
|
||||||
|
tooltipFilename.textContent = `File Name: ${entry[1].fileName}`;
|
||||||
|
|
||||||
|
//tooltip source
|
||||||
|
const tooltipSource = document.createElement('p');
|
||||||
|
tooltipSource.textContent = `Source: ${entry[1].type}`;
|
||||||
|
|
||||||
|
//tooltip duration
|
||||||
|
const tooltipDuration = document.createElement('p');
|
||||||
|
tooltipDuration.textContent = `Duration: ${entry[1].duration}`;
|
||||||
|
|
||||||
|
//tooltip start
|
||||||
|
const tooltipStart = document.createElement('p');
|
||||||
|
tooltipStart.textContent = `Start Time: ${new Date(entry[1].startTime).toLocaleString()}`;
|
||||||
|
|
||||||
|
//tooltip end
|
||||||
|
const tooltipEnd = document.createElement('p');
|
||||||
|
tooltipEnd.textContent = `Start Time: ${new Date(entry[1].startTime + (entry[1].duration * 1000)).toLocaleString()}`;
|
||||||
|
|
||||||
|
//append components
|
||||||
|
for(let component of [
|
||||||
|
tooltipTitle,
|
||||||
|
tooltipFilename,
|
||||||
|
tooltipSource,
|
||||||
|
tooltipDuration,
|
||||||
|
tooltipStart,
|
||||||
|
tooltipEnd
|
||||||
|
]){
|
||||||
|
tooltipDiv.append(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Setup media tooltip
|
||||||
|
entryDiv.addEventListener('mouseenter',(event)=>{utils.ux.displayTooltip(event, tooltipDiv, false, null, true, this.ownerDoc);});
|
||||||
|
|
||||||
|
//context menu
|
||||||
|
const menuMap = new Map([
|
||||||
|
["Play now", ()=>{this.client.socket.emit('move', {uuid: entry[1].uuid})}],
|
||||||
|
["Move To...", ()=>{}],
|
||||||
|
["Delete", ()=>{this.client.socket.emit('delete', {uuid: entry[1].uuid})}],
|
||||||
|
["Open in New Tab", ()=>{window.open(entry[1].url, '_blank').focus();}],
|
||||||
|
["Copy URL", ()=>{navigator.clipboard.writeText(entry[1].url);}],
|
||||||
|
]);
|
||||||
|
|
||||||
|
//Setup context menu
|
||||||
|
entryDiv.addEventListener('contextmenu', (event)=>{utils.ux.displayContextMenu(event, '', menuMap, this.ownerDoc);});
|
||||||
|
|
||||||
//Append entry elements
|
//Append entry elements
|
||||||
entryDiv.append(entryTitle);
|
entryDiv.append(entryTitle);
|
||||||
|
|
@ -236,7 +368,7 @@ class queuePanel extends panelObj{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTimeMarker(date = new Date()){
|
renderTimeMarker(date = new Date(), forceScroll = false){
|
||||||
//Calculate marker position by date
|
//Calculate marker position by date
|
||||||
const markerPosition = Math.round(this.offsetByDate(date));
|
const markerPosition = Math.round(this.offsetByDate(date));
|
||||||
|
|
||||||
|
|
@ -260,12 +392,9 @@ class queuePanel extends panelObj{
|
||||||
this.timeMarker.style.top = `${markerPosition}px`;
|
this.timeMarker.style.top = `${markerPosition}px`;
|
||||||
|
|
||||||
//If the panel document isn't null (we're not actively switching panels)
|
//If the panel document isn't null (we're not actively switching panels)
|
||||||
if(this.panelDocument != null){
|
if(this.panelDocument != null && (this.autoscroll || forceScroll)){
|
||||||
//Grab the current owner document object
|
|
||||||
const ownerDoc = this.panelDocument.ownerDocument == null ? this.panelDocument : this.panelDocument.ownerDocument;
|
|
||||||
|
|
||||||
//Get height difference between window and queue layout controller
|
//Get height difference between window and queue layout controller
|
||||||
const docDifference = ownerDoc.defaultView.innerHeight - this.queueLayoutController.getBoundingClientRect().height;
|
const docDifference = this.ownerDoc.defaultView.innerHeight - this.queueLayoutController.getBoundingClientRect().height;
|
||||||
//Calculate scroll target by body difference and marker position
|
//Calculate scroll target by body difference and marker position
|
||||||
const scrollTarget = (markerPosition - (this.queueLayoutController.getBoundingClientRect().height - docDifference) / 2) + docDifference;
|
const scrollTarget = (markerPosition - (this.queueLayoutController.getBoundingClientRect().height - docDifference) / 2) + docDifference;
|
||||||
//Calculate scroll behavior by distance
|
//Calculate scroll behavior by distance
|
||||||
|
|
@ -292,6 +421,9 @@ class queuePanel extends panelObj{
|
||||||
}
|
}
|
||||||
|
|
||||||
renderQueueScale(inputDate = new Date()){
|
renderQueueScale(inputDate = new Date()){
|
||||||
|
//Clear out queue marker container
|
||||||
|
this.queueMarkerContainer.innerHTML = '';
|
||||||
|
|
||||||
//Make sure we don't modify the date we're handed
|
//Make sure we don't modify the date we're handed
|
||||||
const date = structuredClone(inputDate);
|
const date = structuredClone(inputDate);
|
||||||
|
|
||||||
|
|
@ -350,8 +482,8 @@ class queuePanel extends panelObj{
|
||||||
}
|
}
|
||||||
|
|
||||||
offsetByDate(date = new Date(), bottomOffset = false){
|
offsetByDate(date = new Date(), bottomOffset = false){
|
||||||
//Pull start of day epoch from given date
|
//Pull start of day epoch from given date, make sure to use a new date object so we don't fuck up any date objects passed to us
|
||||||
const dayEpoch = structuredClone(date).setHours(0,0,0,0);
|
const dayEpoch = new Date(date).setHours(0,0,0,0);
|
||||||
//Get difference between now and day epoch to get time since the start of the current day in milliseconds
|
//Get difference between now and day epoch to get time since the start of the current day in milliseconds
|
||||||
const curTime = date.getTime() - dayEpoch;
|
const curTime = date.getTime() - dayEpoch;
|
||||||
//Devide by amount of milliseconds in a day to convert time over to a floating point number between 0 and 1
|
//Devide by amount of milliseconds in a day to convert time over to a floating point number between 0 and 1
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,7 @@ class player{
|
||||||
this.showVideoIcon.style.display = "flex";
|
this.showVideoIcon.style.display = "flex";
|
||||||
this.client.chatBox.hideChatIcon.style.display = "none";
|
this.client.chatBox.hideChatIcon.style.display = "none";
|
||||||
//Need to clear the width from the split, or else it doesn't display properly
|
//Need to clear the width from the split, or else it doesn't display properly
|
||||||
this.client.chatBox.chatPanel.style.width = "100%";
|
this.client.chatBox.chatPanel.style.flexBasis = "100%";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,17 +71,21 @@ class canopyUXUtils{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
displayTooltip(event, content, ajaxTooltip, cb, soft = false){
|
displayTooltip(event, content, ajaxTooltip, cb, soft = false, doc = document){
|
||||||
//Create the tooltip
|
//Create the tooltip
|
||||||
const tooltip = new canopyUXUtils.tooltip(content, ajaxTooltip, ()=>{
|
const tooltip = new canopyUXUtils.tooltip(content, ajaxTooltip, ()=>{
|
||||||
//Call mouse move again after ajax load to re-calculate position within context of the new content
|
//If this is an ajax tooltip
|
||||||
tooltip.moveToMouse(event);
|
if(ajaxTooltip){
|
||||||
|
//Call mouse move again after ajax load to re-calculate position within context of the new content
|
||||||
|
tooltip.moveToMouse(event);
|
||||||
|
}
|
||||||
|
|
||||||
//If we have a callback function
|
//If we have a callback function
|
||||||
if(typeof cb == "function"){
|
if(typeof cb == "function"){
|
||||||
//Call async callback
|
//Call async callback
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
});
|
}, doc);
|
||||||
|
|
||||||
//Move the tooltip with the mouse
|
//Move the tooltip with the mouse
|
||||||
event.target.addEventListener('mousemove', tooltip.moveToMouse.bind(tooltip));
|
event.target.addEventListener('mousemove', tooltip.moveToMouse.bind(tooltip));
|
||||||
|
|
@ -92,18 +96,29 @@ class canopyUXUtils{
|
||||||
//remove the tooltip on mouseleave
|
//remove the tooltip on mouseleave
|
||||||
event.target.addEventListener('mouseleave', tooltip.remove.bind(tooltip));
|
event.target.addEventListener('mouseleave', tooltip.remove.bind(tooltip));
|
||||||
|
|
||||||
|
//Kill tooltip with parent
|
||||||
|
doc.body.addEventListener('mousemove', killWithParent);
|
||||||
|
|
||||||
if(soft){
|
if(soft){
|
||||||
//remove the tooltip on context menu open
|
//remove the tooltip on context menu open
|
||||||
event.target.addEventListener('click', tooltip.remove.bind(tooltip));
|
event.target.addEventListener('click', tooltip.remove.bind(tooltip));
|
||||||
event.target.addEventListener('contextmenu', tooltip.remove.bind(tooltip));
|
event.target.addEventListener('contextmenu', tooltip.remove.bind(tooltip));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function killWithParent(){
|
||||||
|
//If the tooltip parent no longer exists
|
||||||
|
if(!event.target.isConnected){
|
||||||
|
//Kill the whole family :D
|
||||||
|
tooltip.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
displayContextMenu(event, title, menuMap){
|
displayContextMenu(event, title, menuMap, doc){
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
//Create context menu
|
//Create context menu
|
||||||
const contextMenu = new canopyUXUtils.contextMenu(title, menuMap);
|
const contextMenu = new canopyUXUtils.contextMenu(title, menuMap, doc);
|
||||||
|
|
||||||
//Move context menu to mouse
|
//Move context menu to mouse
|
||||||
contextMenu.moveToMouse(event);
|
contextMenu.moveToMouse(event);
|
||||||
|
|
@ -167,12 +182,13 @@ class canopyUXUtils{
|
||||||
}
|
}
|
||||||
|
|
||||||
static tooltip = class{
|
static tooltip = class{
|
||||||
constructor(content, ajaxTooltip = false, cb){
|
constructor(content, ajaxTooltip = false, cb, doc = document){
|
||||||
//Define non-tooltip node values
|
//Define non-tooltip node values
|
||||||
this.content = content;
|
this.content = content;
|
||||||
this.ajaxPopup = ajaxTooltip;
|
this.ajaxPopup = ajaxTooltip;
|
||||||
this.cb = cb;
|
this.cb = cb;
|
||||||
this.id = Math.random();
|
this.id = Math.random();
|
||||||
|
this.doc = doc;
|
||||||
|
|
||||||
//create and append tooltip
|
//create and append tooltip
|
||||||
this.tooltip = document.createElement('div');
|
this.tooltip = document.createElement('div');
|
||||||
|
|
@ -190,7 +206,15 @@ class canopyUXUtils{
|
||||||
this.tooltip.textContent = "Loading tooltip..."
|
this.tooltip.textContent = "Loading tooltip..."
|
||||||
this.tooltip.innerHTML = await utils.ajax.getTooltip(this.content);
|
this.tooltip.innerHTML = await utils.ajax.getTooltip(this.content);
|
||||||
}else{
|
}else{
|
||||||
this.tooltip.innerHTML = this.content;
|
//If the content we received is a string
|
||||||
|
if(typeof this.content == "string"){
|
||||||
|
//Use it
|
||||||
|
this.tooltip.innerHTML = this.content;
|
||||||
|
//Otherwise
|
||||||
|
}else{
|
||||||
|
//Append it as a node
|
||||||
|
this.tooltip.appendChild(this.content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.cb){
|
if(this.cb){
|
||||||
|
|
@ -200,14 +224,14 @@ class canopyUXUtils{
|
||||||
}
|
}
|
||||||
|
|
||||||
displayTooltip(){
|
displayTooltip(){
|
||||||
document.body.appendChild(this.tooltip);
|
this.doc.body.appendChild(this.tooltip);
|
||||||
}
|
}
|
||||||
|
|
||||||
moveToPos(x,y){
|
moveToPos(x,y){
|
||||||
//If the distance between the left edge of the window - the window width is more than the width of our tooltip
|
//If the distance between the left edge of the window - the window width is more than the width of our tooltip
|
||||||
if((window.innerWidth - (window.innerWidth - x)) > this.tooltip.getBoundingClientRect().width){
|
if((this.doc.defaultView.innerWidth - (this.doc.defaultView.innerWidth - x)) > this.tooltip.getBoundingClientRect().width){
|
||||||
//Push it to the right edge of the cursor, where the hard edge typically is
|
//Push it to the right edge of the cursor, where the hard edge typically is
|
||||||
this.tooltip.style.right = `${window.innerWidth - x}px`;
|
this.tooltip.style.right = `${this.doc.defaultView.innerWidth - x}px`;
|
||||||
this.tooltip.style.left = '';
|
this.tooltip.style.left = '';
|
||||||
//otherwise, if we're close to the edge
|
//otherwise, if we're close to the edge
|
||||||
}else{
|
}else{
|
||||||
|
|
@ -218,9 +242,9 @@ class canopyUXUtils{
|
||||||
|
|
||||||
|
|
||||||
//If the distance between the top edge of the window - the window height is more than the heigt of our tooltip
|
//If the distance between the top edge of the window - the window height is more than the heigt of our tooltip
|
||||||
if((window.innerHeight - (window.innerHeight - y)) > this.tooltip.getBoundingClientRect().height){
|
if((this.doc.defaultView.innerHeight - (this.doc.defaultView.innerHeight - y)) > this.tooltip.getBoundingClientRect().height){
|
||||||
//Push it above the mouse
|
//Push it above the mouse
|
||||||
this.tooltip.style.bottom = `${window.innerHeight - y}px`;
|
this.tooltip.style.bottom = `${this.doc.defaultView.innerHeight - y}px`;
|
||||||
this.tooltip.style.top = '';
|
this.tooltip.style.top = '';
|
||||||
//otherwise if we're close to the edge
|
//otherwise if we're close to the edge
|
||||||
}else{
|
}else{
|
||||||
|
|
@ -243,9 +267,9 @@ class canopyUXUtils{
|
||||||
}
|
}
|
||||||
|
|
||||||
static contextMenu = class extends this.tooltip{
|
static contextMenu = class extends this.tooltip{
|
||||||
constructor(title, menuMap){
|
constructor(title, menuMap, doc = document){
|
||||||
//Call inherited tooltip constructor
|
//Call inherited tooltip constructor
|
||||||
super('Loading Menu...');
|
super('Loading Menu...', false, null, doc);
|
||||||
//Set tooltip class
|
//Set tooltip class
|
||||||
this.tooltip.classList.add('context-menu');
|
this.tooltip.classList.add('context-menu');
|
||||||
|
|
||||||
|
|
@ -281,8 +305,8 @@ class canopyUXUtils{
|
||||||
|
|
||||||
//Create event listener to remove tooltip whenever anything is clicked, inside or out of the menu
|
//Create event listener to remove tooltip whenever anything is clicked, inside or out of the menu
|
||||||
//Little hacky but we have to do it a bit later to prevent the opening event from closing the menu
|
//Little hacky but we have to do it a bit later to prevent the opening event from closing the menu
|
||||||
setTimeout(()=>{document.body.addEventListener('click', this.remove.bind(this));}, 1);
|
setTimeout(()=>{this.doc.body.addEventListener('click', this.remove.bind(this));}, 1);
|
||||||
setTimeout(()=>{document.body.addEventListener('contextmenu', this.remove.bind(this));}, 1);
|
setTimeout(()=>{this.doc.body.addEventListener('contextmenu', this.remove.bind(this));}, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -390,7 +414,7 @@ class canopyUXUtils{
|
||||||
}
|
}
|
||||||
|
|
||||||
static clickDragger = class{
|
static clickDragger = class{
|
||||||
constructor(handle, element, leftHandle = true, parent){
|
constructor(handle, element, leftHandle = true, parent, flex = true){
|
||||||
//Pull needed nodes
|
//Pull needed nodes
|
||||||
this.handle = document.querySelector(handle);
|
this.handle = document.querySelector(handle);
|
||||||
this.element = document.querySelector(element);
|
this.element = document.querySelector(element);
|
||||||
|
|
@ -407,6 +431,9 @@ class canopyUXUtils{
|
||||||
//we put a click dragger in yo click dragger so you could click and drag while you click and drag
|
//we put a click dragger in yo click dragger so you could click and drag while you click and drag
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
|
|
||||||
|
//Whether or not click dragger is in a flexbox
|
||||||
|
this.flex = flex;
|
||||||
|
|
||||||
//Setup our event listeners
|
//Setup our event listeners
|
||||||
this.setupInput();
|
this.setupInput();
|
||||||
}
|
}
|
||||||
|
|
@ -453,7 +480,13 @@ class canopyUXUtils{
|
||||||
//if we're not breaking the page, or we're moving left
|
//if we're not breaking the page, or we're moving left
|
||||||
if((!this.breakingScreen && pageBreak <= 0) || event.clientX < this.handle.getBoundingClientRect().left){
|
if((!this.breakingScreen && pageBreak <= 0) || event.clientX < this.handle.getBoundingClientRect().left){
|
||||||
//Apply difference to width
|
//Apply difference to width
|
||||||
|
if(this.flex){
|
||||||
|
this.element.style.flexBasis = `${this.calcWidth(difference)}vw`;
|
||||||
|
}
|
||||||
|
//I know it's kludgy to apply this along-side flex basis but it fixes some nasty bugs with nested draggers
|
||||||
|
//Don't @ me, it's not like i'm an actual web developer anyways :P
|
||||||
this.element.style.width = `${this.calcWidth(difference)}vw`;
|
this.element.style.width = `${this.calcWidth(difference)}vw`;
|
||||||
|
|
||||||
//If we let go here, the width isn't breaking anything so there's nothing to fix.
|
//If we let go here, the width isn't breaking anything so there's nothing to fix.
|
||||||
this.breakingScreen = false;
|
this.breakingScreen = false;
|
||||||
}else{
|
}else{
|
||||||
|
|
@ -477,6 +510,9 @@ class canopyUXUtils{
|
||||||
|
|
||||||
fixCutoff(standalone = true, pageBreak = document.body.scrollWidth - document.body.getBoundingClientRect().width){
|
fixCutoff(standalone = true, pageBreak = document.body.scrollWidth - document.body.getBoundingClientRect().width){
|
||||||
//Fix the page width
|
//Fix the page width
|
||||||
|
if(this.flex){
|
||||||
|
this.element.style.flexBasis = `${this.calcWidth(this.element.getBoundingClientRect().width + pageBreak)}vw`;
|
||||||
|
}
|
||||||
this.element.style.width = `${this.calcWidth(this.element.getBoundingClientRect().width + pageBreak)}vw`;
|
this.element.style.width = `${this.calcWidth(this.element.getBoundingClientRect().width + pageBreak)}vw`;
|
||||||
|
|
||||||
//If we're calling this outside of drag() (regardless of draglock unless set otherwise)
|
//If we're calling this outside of drag() (regardless of draglock unless set otherwise)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue