Initial commit.
This commit is contained in:
commit
f0c91b4e55
78 changed files with 5054 additions and 0 deletions
47
www/css/adminPanel.css
Normal file
47
www/css/adminPanel.css
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
#admin-channel-list-div{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 30%;
|
||||
}
|
||||
|
||||
|
||||
#admin-channel-list-table{
|
||||
border-spacing: 0px;
|
||||
}
|
||||
|
||||
td.admin-channel-list-entry{
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
|
||||
.admin-channel-list-entry-title{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.admin-channel-list-entry-img-row{
|
||||
width: 1.5em;
|
||||
}
|
||||
|
||||
img.admin-channel-list-entry-item{
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
.admin-channel-list-entry-name-row{
|
||||
width: 15%;
|
||||
}
|
||||
213
www/css/channel.css
Normal file
213
www/css/channel.css
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
div#channel-flexbox{
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
div.panel-head-div{
|
||||
height: 1.2em;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.panel-head-element{
|
||||
margin: 0 0.2em 0 0.2em;
|
||||
}
|
||||
|
||||
i.panel-head-element{
|
||||
height: 1.2em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
span.panel-head-spacer-span{
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
div#media-panel-div{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#media-panel-head-div{
|
||||
position: absolute;
|
||||
height: 3em;
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
video#media-panel-video{
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#media-panel-sync-button{
|
||||
height: 1.5em;
|
||||
}
|
||||
|
||||
#media-panel-title-paragraph{
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
div#chat-panel-div{
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
div#chat-panel-main-div{
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 1%;
|
||||
}
|
||||
|
||||
.drag-handle{
|
||||
position: absolute;
|
||||
cursor: ew-resize;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 0.4em;
|
||||
}
|
||||
|
||||
#chat-panel-multipanel-div{
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#chat-panel-buffer-div{
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
#chat-panel-users-div{
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
#chat-area{
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
div#chat-panel-control-div{
|
||||
height: 2em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 0.5em;
|
||||
}
|
||||
|
||||
i.chat-panel-control{
|
||||
height: 1em;
|
||||
margin: auto;
|
||||
margin-left: 0.5em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#chat-panel-settings-icon{
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
#chat-panel-prompt{
|
||||
margin-left: 0.5em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
p#chat-panel-high-level-paragraph{
|
||||
margin: auto;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
p.panel-head-element{
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
#chat-panel-flair-select{
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
input#chat-panel-prompt{
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#chat-panel-send-button{
|
||||
margin: auto;
|
||||
margin-right: 1em;
|
||||
margin-left: 0.5em;
|
||||
height: 1.5em;
|
||||
}
|
||||
|
||||
.chat-entry{
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.chat-entry-username{
|
||||
margin: 0.2em;
|
||||
}
|
||||
|
||||
.chat-entry-body{
|
||||
margin: 0.2em;
|
||||
}
|
||||
|
||||
.chat-entry-high-level{
|
||||
margin: 0.2em;
|
||||
z-index: 2;
|
||||
background-image: url("/img/sweet_leaf_simple.png");
|
||||
background-size: 1.3em;
|
||||
background-repeat: no-repeat;
|
||||
background-position-x: center;
|
||||
background-position-y: top;
|
||||
width: 1.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.chat-entry-high-level-img{
|
||||
position: absolute;
|
||||
height: 1.7em;
|
||||
}
|
||||
|
||||
.user-entry{
|
||||
margin: 0.2em;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
#media-panel-aspect-lock-icon{
|
||||
display: none;
|
||||
}
|
||||
|
||||
#chat-panel-user-count{
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
#media-panel-show-chat-icon{
|
||||
display: none;
|
||||
}
|
||||
|
||||
#chat-panel-show-video-icon{
|
||||
display: none;
|
||||
}
|
||||
51
www/css/global.css
Normal file
51
www/css/global.css
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
html{
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body{
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
}
|
||||
|
||||
#navbar{
|
||||
display: flex;
|
||||
padding: 0.5em;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.navbar-item{
|
||||
display: inline;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#instance-title{
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
p.navbar-item, input.navbar-item{
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.navbar-item input{
|
||||
padding: 0.2em;
|
||||
}
|
||||
60
www/css/index.css
Normal file
60
www/css/index.css
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
span.channel-guide-entry-item{
|
||||
overflow: scroll;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
p.channel-guide-entry{
|
||||
font-size: 0.8em;
|
||||
margin-left: 0;
|
||||
}
|
||||
25
www/css/newChannel.css
Normal file
25
www/css/newChannel.css
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
form{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 5% 17%;
|
||||
}
|
||||
|
||||
input{
|
||||
margin: 0 0 2em;
|
||||
}
|
||||
43
www/css/profile.css
Normal file
43
www/css/profile.css
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
p.profile-item-edit{
|
||||
display: inline;
|
||||
}
|
||||
|
||||
span.profile-item{
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
p.profile-item-label{
|
||||
display: inline;
|
||||
}
|
||||
|
||||
span.account-settings{
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
input.account-settings-password-reset{
|
||||
display: block;
|
||||
}
|
||||
|
||||
a#account-settings-delete-link{
|
||||
font-weight: bold;
|
||||
}
|
||||
24
www/css/register.css
Normal file
24
www/css/register.css
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
form{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 5% 17%;
|
||||
}
|
||||
|
||||
input{
|
||||
margin: 0 0 2em;
|
||||
}
|
||||
245
www/css/theme/movie-night.css
Normal file
245
www/css/theme/movie-night.css
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
:root{
|
||||
--main-font: "open-sans", sans-serif;
|
||||
--bg0: rgb(158, 158, 158);
|
||||
--bg1: rgb(70, 70, 70);
|
||||
--bg2: rgb(220, 220, 220);
|
||||
--bg1-alt0: rgb(30, 30, 30);
|
||||
--bg2-alt0: rgb(200, 200, 200);
|
||||
--bg2-alt1: rgb(180, 180, 180);
|
||||
|
||||
--accent0: rgb(48, 47, 47);
|
||||
--accent0-alt0: rgb(34, 34, 34);
|
||||
--accent0-warning: firebrick;
|
||||
--accent1: rgb(245, 245, 245);
|
||||
--accent1-alt0: rgb(185, 185, 185);
|
||||
--accent2: var(--accent0-alt0);
|
||||
|
||||
--focus0: rgb(51, 153, 51);
|
||||
|
||||
--userlist-color0:rgb(87, 145, 97);
|
||||
--userlist-color1:rgb(143, 46, 26);
|
||||
--userlist-color2:rgb(51, 101, 161);
|
||||
--userlist-color3:rgb(110, 94, 13);
|
||||
--userlist-color4:rgb(129, 43, 43);
|
||||
--userlist-color5:rgb(150, 64, 6);
|
||||
--userlist-color6:rgb(111, 61, 204);
|
||||
|
||||
--media-header-gradient: linear-gradient(180deg, var(--bg1-alt0) 0%, #FFFFFF00 76%);
|
||||
}
|
||||
|
||||
body{
|
||||
background-color: var(--bg0);
|
||||
font-family: var(--main-font);
|
||||
color: var(--accent0);
|
||||
}
|
||||
|
||||
a{
|
||||
text-decoration: none;
|
||||
color: var(--accent0);
|
||||
}
|
||||
|
||||
select{
|
||||
background-color: var(--bg2);
|
||||
border-radius: 0.5em;
|
||||
border: none;
|
||||
}
|
||||
|
||||
button{
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
a:hover{
|
||||
color: var(--accent0-alt0);
|
||||
}
|
||||
|
||||
button{
|
||||
background-color: var(--bg0);
|
||||
color: var(--accent0);
|
||||
border: none;
|
||||
}
|
||||
|
||||
#navbar{
|
||||
background-color: var(--bg1);
|
||||
}
|
||||
|
||||
.navbar-item{
|
||||
color: var(--accent1);
|
||||
border: hidden;
|
||||
}
|
||||
|
||||
a:hover.navbar-item{
|
||||
color: var(--accent1-alt0);
|
||||
}
|
||||
|
||||
.navbar-item input{
|
||||
background-color: var(--bg1-alt0);
|
||||
}
|
||||
|
||||
.channel-guide-entry{
|
||||
background-color: var(--bg1);
|
||||
color: var(--accent1);
|
||||
}
|
||||
|
||||
div.channel-guide-entry{
|
||||
border-radius: 0.3em;
|
||||
box-shadow: 0.2em 0.2em 0.1em var(--bg1-alt0) inset;
|
||||
}
|
||||
|
||||
a.channel-guide-entry-item{
|
||||
color: var(--accent1);
|
||||
}
|
||||
|
||||
a:hover.channel-guide-entry-item{
|
||||
color: var(--accent1-alt0);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
a#account-settings-delete-link{
|
||||
color: var(--accent0-warning);
|
||||
}
|
||||
|
||||
#channel-delete{
|
||||
color: var(--accent0-warning);
|
||||
}
|
||||
|
||||
#admin-channel-list-table{
|
||||
background-color: var(--bg1);
|
||||
color: var(--accent1);
|
||||
}
|
||||
|
||||
tr.admin-channel-list-entry{
|
||||
box-shadow: var(--accent1) 0px 1em 1px -1em, var(--accent1) 0px -1em 1px -1em;
|
||||
}
|
||||
|
||||
td.admin-channel-list-entry-name-row{
|
||||
box-shadow: var(--accent1) 1em 0px 1px -1em, var(--accent1) -1em 0px 1px -1em;
|
||||
}
|
||||
|
||||
a.admin-channel-list-entry-item{
|
||||
color: var(--accent1);
|
||||
}
|
||||
|
||||
a:hover.admin-channel-list-entry-item{
|
||||
color: var(--accent1-alt0);
|
||||
}
|
||||
|
||||
#media-panel-div{
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
#chat-panel-buffer-div{
|
||||
background-color: var(--bg2);
|
||||
}
|
||||
|
||||
#chat-panel-control-div{
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#chat-panel-control-div:focus-within{
|
||||
box-shadow: 2px 2px 3px var(--focus0), -2px 2px 3px var(--focus0), 2px -2px 3px var(--focus0), -2px -2px 3px var(--focus0);
|
||||
}
|
||||
|
||||
#chat-area{
|
||||
background-color: var(--bg2);
|
||||
}
|
||||
|
||||
div#chat-panel-control-div{
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
#chat-panel-prompt{
|
||||
border: none;
|
||||
}
|
||||
|
||||
#chat-panel-prompt:focus{
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.chat-entry{
|
||||
background-color: var(--bg2);
|
||||
border-bottom: 1px solid var(--bg2-alt1);
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.userlist-color0{/*green0*/
|
||||
color: var(--userlist-color0);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.userlist-color1{/*red0*/
|
||||
color: var(--userlist-color1);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.userlist-color2{/*blue0*/
|
||||
color: var(--userlist-color2);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.userlist-color3{/*tan0*/
|
||||
color: var(--userlist-color3);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.userlist-color4{/*pink0*/
|
||||
color: var(--userlist-color4);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.userlist-color5{/*orange*/
|
||||
color: var(--userlist-color5);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.userlist-color6{/*violet*/
|
||||
color: var(--userlist-color6);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.chat-entry-high-level{
|
||||
text-shadow: 1px 1px 1px white, -1px -1px 1px white, 1px 1px 1px white, -1px 1px 1px white, 1px -1px 1px white;
|
||||
}
|
||||
|
||||
#media-panel-head-div{
|
||||
background: rgb(2,0,36);
|
||||
background: var(--media-header-gradient);
|
||||
color: var(--accent1-alt0);
|
||||
}
|
||||
|
||||
#chat-panel-send-button{
|
||||
background-color: var(--focus0);
|
||||
color: white;
|
||||
}
|
||||
|
||||
select.panel-head-element{
|
||||
height: 1.2em;
|
||||
margin: auto;
|
||||
}
|
||||
190
www/css/theme/the_network_classic.css
Normal file
190
www/css/theme/the_network_classic.css
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
:root{
|
||||
--main-font: "open-sans", sans-serif;
|
||||
--bg0: rgb(158, 158, 158);
|
||||
--bg1: rgb(70, 70, 70);
|
||||
--bg2: rgb(220, 220, 220);
|
||||
--bg1-alt0: rgb(30, 30, 30);
|
||||
--bg2-alt0: rgb(200, 200, 200);
|
||||
--bg2-alt1: rgb(180, 180, 180);
|
||||
|
||||
--accent0: rgb(48, 47, 47);
|
||||
--accent0-alt0: rgb(34, 34, 34);
|
||||
--accent0-warning: firebrick;
|
||||
--accent1: rgb(245, 245, 245);
|
||||
--accent1-alt0: rgb(185, 185, 185);
|
||||
--accent2: var(--accent0-alt0);
|
||||
|
||||
|
||||
--userlist-color0:rgb(122, 199, 135);
|
||||
--userlist-color1:rgb(242, 104, 77);
|
||||
--userlist-color2:rgb(77, 150, 239);
|
||||
--userlist-color3:rgb(247, 241, 212);
|
||||
--userlist-color4:rgb(255, 173, 173);
|
||||
--userlist-color5:rgb(254, 151, 82);
|
||||
--userlist-color6:rgb(209, 167, 246);
|
||||
}
|
||||
|
||||
body{
|
||||
background-color: var(--bg0);
|
||||
font-family: var(--main-font);
|
||||
color: var(--accent0);
|
||||
}
|
||||
|
||||
a{
|
||||
text-decoration: none;
|
||||
color: var(--accent0);
|
||||
}
|
||||
|
||||
a:hover{
|
||||
color: var(--accent0-alt0);
|
||||
}
|
||||
|
||||
#navbar{
|
||||
background-color: var(--bg1);
|
||||
}
|
||||
|
||||
.navbar-item{
|
||||
color: var(--accent1);
|
||||
border: hidden;
|
||||
}
|
||||
|
||||
a:hover.navbar-item{
|
||||
color: var(--accent1-alt0);
|
||||
}
|
||||
|
||||
.navbar-item input{
|
||||
background-color: var(--bg1-alt0);
|
||||
}
|
||||
|
||||
.channel-guide-entry{
|
||||
background-color: var(--bg1);
|
||||
color: var(--accent1);
|
||||
}
|
||||
|
||||
div.channel-guide-entry{
|
||||
border-radius: 0.3em;
|
||||
box-shadow: 0.2em 0.2em 0.1em var(--bg1-alt0) inset;
|
||||
}
|
||||
|
||||
a.channel-guide-entry-item{
|
||||
color: var(--accent1);
|
||||
}
|
||||
|
||||
a:hover.channel-guide-entry-item{
|
||||
color: var(--accent1-alt0);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
a#account-settings-delete-link{
|
||||
color: var(--accent0-warning);
|
||||
}
|
||||
|
||||
#channel-delete{
|
||||
color: var(--accent0-warning);
|
||||
}
|
||||
|
||||
#admin-channel-list-table{
|
||||
background-color: var(--bg1);
|
||||
color: var(--accent1);
|
||||
}
|
||||
|
||||
tr.admin-channel-list-entry{
|
||||
box-shadow: var(--accent1) 0px 1em 1px -1em, var(--accent1) 0px -1em 1px -1em;
|
||||
}
|
||||
|
||||
td.admin-channel-list-entry-name-row{
|
||||
box-shadow: var(--accent1) 1em 0px 1px -1em, var(--accent1) -1em 0px 1px -1em;
|
||||
}
|
||||
|
||||
a.admin-channel-list-entry-item{
|
||||
color: var(--accent1);
|
||||
}
|
||||
|
||||
a:hover.admin-channel-list-entry-item{
|
||||
color: var(--accent1-alt0);
|
||||
}
|
||||
|
||||
#media-panel-div{
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
#chat-panel-buffer-div{
|
||||
background-color: var(--bg2);
|
||||
}
|
||||
|
||||
.chat-entry{
|
||||
display: flex;
|
||||
background-color: var(--bg2);
|
||||
border-bottom: 1px solid var(--bg2-alt1);
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.chat-entry-username{
|
||||
margin: 0.2em;
|
||||
}
|
||||
|
||||
.chat-entry-body{
|
||||
margin: 0.2em;
|
||||
}
|
||||
|
||||
.userlist-color0{/*green0*/
|
||||
color: var(--userlist-color0);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.userlist-color1{/*red0*/
|
||||
color: var(--userlist-color1);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.userlist-color2{/*blue0*/
|
||||
color: var(--userlist-color2);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.userlist-color3{/*tan0*/
|
||||
color: var(--userlist-color3);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.userlist-color4{/*pink0*/
|
||||
color: var(--userlist-color4);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.userlist-color5{/*orange*/
|
||||
color: var(--userlist-color5);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.userlist-color6{/*violet*/
|
||||
color: var(--userlist-color6);
|
||||
text-shadow: none;
|
||||
}
|
||||
BIN
www/img/frst.jpg
Normal file
BIN
www/img/frst.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 MiB |
BIN
www/img/frstdusk.png
Normal file
BIN
www/img/frstdusk.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 MiB |
BIN
www/img/johnny.png
Normal file
BIN
www/img/johnny.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
www/img/sweet_leaf_simple.png
Normal file
BIN
www/img/sweet_leaf_simple.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
47
www/js/channel/channel.js
Normal file
47
www/js/channel/channel.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
class channel{
|
||||
constructor(){
|
||||
//Establish connetion to the server via socket.io
|
||||
this.connect();
|
||||
//Define socket listeners
|
||||
this.defineListeners();
|
||||
|
||||
//Scrape channel name off URL
|
||||
this.channelName = window.location.pathname.split('/c/')[`1`];
|
||||
|
||||
//Create the Video Player Object
|
||||
this.player = new player(this);
|
||||
//Create the Chat Box Object
|
||||
this.chatBox = new chatBox(this);
|
||||
//Create the User List Object
|
||||
this.userList = new userList(this);
|
||||
}
|
||||
|
||||
connect(){
|
||||
this.socket = io();
|
||||
}
|
||||
|
||||
defineListeners(){
|
||||
//This function should serve mostly to glue functions from channel and it's children to it's socket's listeners.
|
||||
this.socket.on("connect", () => {
|
||||
document.title = `${this.channelName} - Connected`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const client = new channel();
|
||||
149
www/js/channel/chat.js
Normal file
149
www/js/channel/chat.js
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
class chatBox{
|
||||
constructor(client){
|
||||
//Client Object
|
||||
this.client = client
|
||||
|
||||
//Booleans
|
||||
this.aspectLock = true;
|
||||
|
||||
//clickDragger object
|
||||
this.clickDragger = new canopyUXUtils.clickDragger("#chat-panel-drag-handle", "#chat-panel-div", "#chat-panel-user-count");
|
||||
|
||||
//Element Nodes
|
||||
this.chatPanel = document.querySelector("#chat-panel-div");
|
||||
this.chatBuffer = document.querySelector("#chat-panel-buffer-div");
|
||||
this.chatPrompt = document.querySelector("#chat-panel-prompt");
|
||||
this.sendButton = document.querySelector("#chat-panel-send-button");
|
||||
this.highLevel = document.querySelector("#chat-panel-high-level-select");
|
||||
//Seems weird to stick this in here, but the split is dictated by chat width :P
|
||||
this.aspectLockIcon = document.querySelector("#media-panel-aspect-lock-icon");
|
||||
this.hideChatIcon = document.querySelector("#chat-panel-div-hide");
|
||||
this.showChatIcon = document.querySelector("#media-panel-show-chat-icon");
|
||||
|
||||
//Setup functions
|
||||
this.setupInput();
|
||||
this.defineListeners();
|
||||
this.sizeToAspect();
|
||||
}
|
||||
|
||||
setupInput(){
|
||||
//Chat bar
|
||||
this.chatPrompt.addEventListener("keydown", this.send.bind(this));
|
||||
this.sendButton.addEventListener("click", this.send.bind(this));
|
||||
|
||||
//Header icons
|
||||
this.aspectLockIcon.addEventListener("click", this.lockAspect.bind(this));
|
||||
this.showChatIcon.addEventListener("click", ()=>{this.toggleUI()});
|
||||
this.hideChatIcon.addEventListener("click", ()=>{this.toggleUI()});
|
||||
|
||||
//Clickdragger/Resize
|
||||
this.clickDragger.handle.addEventListener("mousedown", this.unlockAspect.bind(this));
|
||||
window.addEventListener("resize", this.resizeAspect.bind(this));
|
||||
}
|
||||
|
||||
defineListeners(){
|
||||
this.client.socket.on("chat-message", (data) => {
|
||||
this.displayChat(data);
|
||||
});
|
||||
}
|
||||
|
||||
lockAspect(event){
|
||||
//prevent the user from breaking shit :P
|
||||
if(this.chatPanel.style.display != "none"){
|
||||
this.aspectLock = true;
|
||||
this.aspectLockIcon.style.display = "none";
|
||||
this.sizeToAspect();
|
||||
}
|
||||
}
|
||||
|
||||
unlockAspect(event){
|
||||
this.aspectLock = false;
|
||||
this.aspectLockIcon.style.display = "inline";
|
||||
}
|
||||
|
||||
resizeAspect(event){
|
||||
if(this.aspectLock){
|
||||
this.sizeToAspect();
|
||||
}
|
||||
}
|
||||
|
||||
sizeToAspect(){
|
||||
if(this.chatPanel.style.display != "none"){
|
||||
var targetVidWidth = this.client.player.getRatio() * this.chatPanel.getBoundingClientRect().height;
|
||||
|
||||
this.chatPanel.style.width = `${(document.body.getBoundingClientRect().width - targetVidWidth)}px`;
|
||||
|
||||
//Fix busted layout
|
||||
var pageBreak = document.body.scrollWidth - document.body.getBoundingClientRect().width;
|
||||
this.chatPanel.style.width = `${this.chatPanel.getBoundingClientRect().width + pageBreak}px`;
|
||||
}
|
||||
}
|
||||
|
||||
toggleUI(show = this.chatPanel.style.display == "none"){
|
||||
if(show){
|
||||
this.chatPanel.style.display = "flex";
|
||||
this.showChatIcon.style.display = "none";
|
||||
this.client.player.hideVideoIcon.style.display = "flex";
|
||||
}else{
|
||||
this.chatPanel.style.display = "none";
|
||||
this.showChatIcon.style.display = "flex";
|
||||
this.client.player.hideVideoIcon.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
displayChat(chat){
|
||||
//Create chat-entry span
|
||||
var chatEntry = document.createElement('span');
|
||||
chatEntry.classList.add("chat-panel-buffer","chat-entry",`chat-entry-${chat.user}`);
|
||||
|
||||
//Create high-level label
|
||||
var highLevel = document.createElement('p');
|
||||
highLevel.classList.add("chat-panel-buffer","chat-entry-high-level");
|
||||
highLevel.innerHTML = `${chat.high}`;
|
||||
chatEntry.appendChild(highLevel);
|
||||
|
||||
//Create username label
|
||||
var userLabel = document.createElement('p');
|
||||
userLabel.classList.add("chat-panel-buffer","chat-entry-username");
|
||||
|
||||
//Create color span
|
||||
var colorSpan = document.createElement('span');
|
||||
colorSpan.classList.add(this.client.userList.colorMap.get(chat.user));
|
||||
colorSpan.innerHTML = `${chat.user}`;
|
||||
userLabel.innerHTML = `${colorSpan.outerHTML}: `;
|
||||
|
||||
chatEntry.appendChild(userLabel);
|
||||
|
||||
//Create chat body
|
||||
var chatBody = document.createElement('p');
|
||||
chatBody.classList.add("chat-panel-buffer","chat-entry-body");
|
||||
chatBody.innerHTML = chat.msg;
|
||||
chatEntry.appendChild(chatBody);
|
||||
|
||||
this.chatBuffer.appendChild(chatEntry);
|
||||
}
|
||||
|
||||
async send(event){
|
||||
if((!event || !event.key || event.key == "Enter") && this.chatPrompt.value){
|
||||
this.client.socket.emit("chat-message",{msg: this.chatPrompt.value, high: this.highLevel.value});
|
||||
this.chatPrompt.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
97
www/js/channel/player.js
Normal file
97
www/js/channel/player.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
class player{
|
||||
constructor (client){
|
||||
//client obj
|
||||
this.client = client;
|
||||
|
||||
//booleans
|
||||
this.onUI = false;
|
||||
|
||||
//timers
|
||||
this.uiTimer = setTimeout(this.toggleUI.bind(this), 1500, false);
|
||||
|
||||
//elements
|
||||
this.playerDiv = document.querySelector("#media-panel-div");
|
||||
this.navBar = document.querySelector("#navbar");
|
||||
this.video = document.querySelector("#media-panel-video");
|
||||
this.uiBar = document.querySelector("#media-panel-head-div");
|
||||
this.showVideoIcon = document.querySelector("#chat-panel-show-video-icon");
|
||||
this.hideVideoIcon = document.querySelector("#media-panel-div-toggle-icon");
|
||||
this.cinemaModeIcon = document.querySelector("#media-panel-cinema-mode-icon");
|
||||
|
||||
//run setup functions
|
||||
this.setupInput();
|
||||
}
|
||||
|
||||
setupInput(){
|
||||
//UIBar Movement Detection
|
||||
this.playerDiv.addEventListener("mousemove", this.popUI.bind(this));
|
||||
this.uiBar.addEventListener("mouseenter", ()=>{this.setOnUI(true)});
|
||||
this.uiBar.addEventListener("mouseleave", ()=>{this.setOnUI(false)});
|
||||
|
||||
//UIBar/header icons
|
||||
this.showVideoIcon.addEventListener("click", ()=>{this.toggleVideo()});
|
||||
this.hideVideoIcon.addEventListener("click", ()=>{this.toggleVideo()});
|
||||
this.cinemaModeIcon.addEventListener("click", ()=>{this.toggleCinemaMode()});
|
||||
}
|
||||
|
||||
popUI(event){
|
||||
this.toggleUI(true);
|
||||
clearTimeout(this.uiTimer);
|
||||
if(!this.onUI){
|
||||
this.uiTimer = setTimeout(this.toggleUI.bind(this), 1500, false);
|
||||
}
|
||||
}
|
||||
|
||||
toggleUI(show = this.uiBar.style.display == "none"){
|
||||
this.uiBar.style.display = show ? "flex" : "none";
|
||||
}
|
||||
|
||||
toggleVideo(show = this.playerDiv.style.display == "none"){
|
||||
if(show){
|
||||
this.playerDiv.style.display = "flex";
|
||||
this.showVideoIcon.style.display = "none";
|
||||
this.client.chatBox.hideChatIcon.style.display = "flex";
|
||||
//Lock the chat to aspect ratio of the video, to make sure the chat width isn't breaking shit
|
||||
this.client.chatBox.lockAspect();
|
||||
}else{
|
||||
this.playerDiv.style.display = "none";
|
||||
this.showVideoIcon.style.display = "flex";
|
||||
this.client.chatBox.hideChatIcon.style.display = "none";
|
||||
//Need to clear the width from the split, or else it doesn't display properly
|
||||
this.client.chatBox.chatPanel.style.width = "100%";
|
||||
}
|
||||
}
|
||||
|
||||
toggleCinemaMode(cinema = this.navBar.style.display == "none"){
|
||||
if(cinema){
|
||||
this.navBar.style.display = "flex";
|
||||
}else{
|
||||
this.navBar.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
setOnUI(onUI){
|
||||
this.onUI = onUI;
|
||||
this.popUI();
|
||||
}
|
||||
|
||||
getRatio(){
|
||||
return this.video.videoWidth / this.video.videoHeight;
|
||||
}
|
||||
}
|
||||
107
www/js/channel/userlist.js
Normal file
107
www/js/channel/userlist.js
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
class userList{
|
||||
constructor(client){
|
||||
//Client object
|
||||
this.client = client
|
||||
|
||||
//Click Dragger Object
|
||||
this.clickDragger = new canopyUXUtils.clickDragger("#chat-panel-users-drag-handle", "#chat-panel-users-div");
|
||||
|
||||
//Strings
|
||||
this.userColors = [
|
||||
"userlist-color0",
|
||||
"userlist-color1",
|
||||
"userlist-color2",
|
||||
"userlist-color3",
|
||||
"userlist-color4",
|
||||
"userlist-color5",
|
||||
"userlist-color6"];
|
||||
|
||||
//Maps
|
||||
this.colorMap = new Map();
|
||||
|
||||
//Element Nodes
|
||||
this.userDiv = document.querySelector("#chat-panel-users-div");
|
||||
this.userList = document.querySelector("#chat-panel-users-list-div");
|
||||
this.userCount = document.querySelector("#chat-panel-user-count");
|
||||
this.toggleIcon = document.querySelector("#chat-panel-users-toggle");
|
||||
|
||||
//Call setup functions
|
||||
this.setupInput();
|
||||
this.defineListeners();
|
||||
}
|
||||
|
||||
//Setup functions
|
||||
setupInput(){
|
||||
this.toggleIcon.addEventListener("click", ()=>{this.toggleUI()});
|
||||
this.userCount.addEventListener("click", ()=>{this.toggleUI()});
|
||||
}
|
||||
|
||||
defineListeners(){
|
||||
this.client.socket.on('user-list', (data) => {
|
||||
this.updateList(data);
|
||||
});
|
||||
}
|
||||
|
||||
updateList(list){
|
||||
//Clear list and set user count
|
||||
this.userCount.textContent = list.length == 1 ? '1 User' : `${list.length} Users`;
|
||||
this.userList.innerHTML = null;
|
||||
|
||||
//create a new map
|
||||
var newMap = new Map();
|
||||
|
||||
//for each user
|
||||
list.forEach((user) => {
|
||||
//randomly pick a color
|
||||
var color = this.userColors[Math.floor(Math.random()*this.userColors.length)]
|
||||
|
||||
//if this user was in the previous colormap
|
||||
if(this.colorMap.get(user) != null){
|
||||
//Override with previous color
|
||||
color = this.colorMap.get(user);
|
||||
}
|
||||
|
||||
newMap.set(user, color);
|
||||
this.renderUser(user, color);
|
||||
});
|
||||
|
||||
this.colorMap = newMap;
|
||||
}
|
||||
|
||||
renderUser(user, color){
|
||||
var userEntry = document.createElement('p');
|
||||
|
||||
userEntry.innerText = user;
|
||||
userEntry.id = `user-entry-${user}`;
|
||||
userEntry.classList.add("chat-panel-users","user-entry",color);
|
||||
|
||||
this.userList.appendChild(userEntry);
|
||||
}
|
||||
|
||||
toggleUI(show = this.userDiv.style.display == "none"){
|
||||
if(show){
|
||||
this.userDiv.style.display = "flex";
|
||||
this.toggleIcon.classList.replace("bi-caret-left-fill","bi-caret-down-fill");
|
||||
}else{
|
||||
this.userDiv.style.display = "none";
|
||||
this.toggleIcon.classList.replace("bi-caret-down-fill","bi-caret-left-fill");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
54
www/js/channelSettings.js
Normal file
54
www/js/channelSettings.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
class channelSettingsPrompt{
|
||||
constructor(){
|
||||
this.channel = window.location.pathname.slice(3).replace('/settings','');
|
||||
this.hidden = document.querySelector("#channel-hidden");
|
||||
this.delete = document.querySelector("#channel-delete");
|
||||
|
||||
this.hidden.addEventListener('change', this.submitUpdate.bind(this));
|
||||
this.delete.addEventListener('click', this.promptDelete.bind(this));
|
||||
}
|
||||
|
||||
async submitUpdate(event){
|
||||
//probably not the cleanest way to get the chan name :P
|
||||
const key = event.target.id.split("-").pop();
|
||||
const value = event.target.type == "checkbox" ? event.target.checked : event.target.value;
|
||||
const settingsMap = new Map([
|
||||
[key, value]
|
||||
]);
|
||||
|
||||
this.handleUpdate(await utils.ajax.setChannelSetting(this.channel, settingsMap));
|
||||
}
|
||||
|
||||
handleUpdate(updateObj){
|
||||
this.hidden.checked = updateObj.hidden;
|
||||
}
|
||||
|
||||
promptDelete(){
|
||||
var confirm = window.prompt(`Warning: You are about to nuke ${this.channel} off of the face of the fucking planet, no taksie-backsies. \n \n Type in ${this.channel} to confirm.`);
|
||||
this.deleteChannel(confirm);
|
||||
}
|
||||
|
||||
async deleteChannel(confirm){
|
||||
if(this.channel === confirm){
|
||||
utils.ajax.deleteChannel(this.channel, confirm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new channelSettingsPrompt();
|
||||
36
www/js/navbar.js
Normal file
36
www/js/navbar.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
//I could make a class like the others but it's so god-damned basic why bother?
|
||||
async function navbarLogin(event){
|
||||
if(!event || !event.key || event.key == "Enter"){
|
||||
var user = document.querySelector("#username-prompt").value;
|
||||
var pass = document.querySelector("#password-prompt").value;
|
||||
|
||||
utils.ajax.login(user, pass);
|
||||
}
|
||||
}
|
||||
|
||||
//assign events
|
||||
if(document.querySelector("#username-prompt")){
|
||||
document.querySelector("#username-prompt").addEventListener("keydown", navbarLogin);
|
||||
document.querySelector("#password-prompt").addEventListener("keydown", navbarLogin);
|
||||
document.querySelector("#login-button").addEventListener("click", navbarLogin);
|
||||
}
|
||||
|
||||
if(document.querySelector("#logout-button")){
|
||||
document.querySelector("#logout-button").addEventListener("click", utils.ajax.logout);
|
||||
}
|
||||
31
www/js/newChannel.js
Normal file
31
www/js/newChannel.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
//I could make a class like the others but it's so god-damned basic why bother?
|
||||
async function registerPrompt(event){
|
||||
if(!event || event.key == "Enter"){
|
||||
var name = document.querySelector("#register-channel-name").value;
|
||||
var description = document.querySelector("#register-description").value;
|
||||
var thumbnail = document.querySelector("#register-thumbnail").value;
|
||||
|
||||
utils.ajax.newChannel(name, description, thumbnail);
|
||||
}
|
||||
}
|
||||
|
||||
//assign events
|
||||
document.querySelector("#register-channel-name").addEventListener("keydown", registerPrompt)
|
||||
document.querySelector("#register-description").addEventListener("keydown", registerPrompt)
|
||||
document.querySelector("#register-thumbnail").addEventListener("keydown", registerPrompt)
|
||||
184
www/js/profile.js
Normal file
184
www/js/profile.js
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
//Base Class
|
||||
class profileEditPrompt{
|
||||
constructor(field, content, useTextArea = false){
|
||||
this.field = field;
|
||||
this.useTextArea = useTextArea;
|
||||
this.content = content;
|
||||
this.link = document.querySelector(`#profile-${field}-edit`);
|
||||
|
||||
//Bail out if something ain't right
|
||||
if(!this.link || !this.content){
|
||||
return;
|
||||
}
|
||||
|
||||
this.setupPrompt();
|
||||
}
|
||||
|
||||
setupPrompt(){
|
||||
if(this.link != null){
|
||||
this.link.addEventListener("click", this.prompt.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
prompt(){
|
||||
//Create input element
|
||||
if(this.useTextArea){
|
||||
this.prompt = document.createElement("textArea");
|
||||
}else{
|
||||
this.prompt = document.createElement("input");
|
||||
}
|
||||
|
||||
//Setup properties
|
||||
this.prompt.id = `profile-${this.field}-prompt`;
|
||||
this.prompt.classList.add("profile-edit-prompt");
|
||||
this.prompt.placeholder = this.content.innerHTML;
|
||||
|
||||
//Setup event listener
|
||||
this.prompt.addEventListener("keydown", this.update.bind(this));
|
||||
|
||||
//replace label
|
||||
this.content.replaceWith(this.prompt);
|
||||
}
|
||||
|
||||
async update(event){
|
||||
if((!event || event.key == "Enter") && this.prompt.value){
|
||||
//setup object
|
||||
var updateObj = {};
|
||||
updateObj[this.field] = this.prompt.value;
|
||||
|
||||
//contact server, and collect response
|
||||
var response = await utils.ajax.updateProfile(updateObj);
|
||||
var updated_content = (await response.json())[this.field];
|
||||
|
||||
//Update label
|
||||
if(response.status == 200){
|
||||
if(this.field == "img"){
|
||||
this.content.src = updated_content;
|
||||
}else{
|
||||
this.content.innerHTML = updated_content;
|
||||
}
|
||||
}
|
||||
this.finish();
|
||||
}else if(event.key == "Escape" || event.key == "Enter"){
|
||||
this.finish();
|
||||
}
|
||||
}
|
||||
|
||||
finish(){
|
||||
this.prompt.replaceWith(this.content);
|
||||
}
|
||||
}
|
||||
|
||||
class profileTextEditPrompt extends profileEditPrompt{
|
||||
constructor(field, useTextArea = false){
|
||||
//Get content based on field name
|
||||
var content = document.querySelector(`#profile-${field}-content`);
|
||||
//Derived Constructor
|
||||
super(field, content, useTextArea);
|
||||
}
|
||||
|
||||
prompt(){
|
||||
super.prompt();
|
||||
}
|
||||
|
||||
async update(event){
|
||||
await super.update(event)
|
||||
}
|
||||
}
|
||||
|
||||
//Child Classes
|
||||
class profileImgEditPrompt extends profileEditPrompt{
|
||||
constructor(){
|
||||
//Get content based on field name
|
||||
var content = document.querySelector(`#profile-img`);
|
||||
//Derived constructor
|
||||
super("img", content, false);
|
||||
}
|
||||
}
|
||||
|
||||
class passwordResetPrompt{
|
||||
constructor(){
|
||||
this.oldPassNode = document.querySelector('#account-settings-password-reset-old');
|
||||
this.newPassNode = document.querySelector('#account-settings-password-reset-new');
|
||||
this.confirmPassNode = document.querySelector('#account-settings-password-reset-confirm');
|
||||
|
||||
this.setupInput(this.oldPassNode);
|
||||
this.setupInput(this.newPassNode);
|
||||
this.setupInput(this.confirmPassNode);
|
||||
}
|
||||
|
||||
setupInput(node){
|
||||
if(node != null){
|
||||
node.addEventListener("keydown", this.update.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
async update(event){
|
||||
var hasVal = (this.oldPassNode.value && this.newPassNode.value && this.confirmPassNode.value);
|
||||
if((!event || event.key == "Enter") && hasVal){
|
||||
if(this.newPassNode.value == this.confirmPassNode.value){
|
||||
const updateObj = {};
|
||||
|
||||
updateObj.passChange = {
|
||||
oldPass: this.oldPassNode.value,
|
||||
newPass: this.newPassNode.value,
|
||||
confirmPass: this.confirmPassNode.value
|
||||
};
|
||||
|
||||
const response = await utils.ajax.updateProfile(updateObj);
|
||||
|
||||
if(response.status == 200){
|
||||
//Return user homepage after good pass change, as we've probably been logged out by the server for security.
|
||||
window.location.pathname = '/';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class deleteAccountPrompt{
|
||||
constructor(){
|
||||
this.deleteLink = document.querySelector('#account-settings-delete-link');
|
||||
this.setupEvent();
|
||||
}
|
||||
|
||||
setupEvent(){
|
||||
if(this.deleteLink != null){
|
||||
this.deleteLink.addEventListener("click",this.deletePrompt);
|
||||
}
|
||||
}
|
||||
|
||||
async deletePrompt(event){
|
||||
const pass = window.prompt("Warning: You are about to nuke your account off of the face of the fucking planet, no taksie-backsies.\n \n (todo: replace with dialog that has obscured password input) \n Enter your password to confirm.");
|
||||
const response = await utils.ajax.deleteAccount(pass);
|
||||
|
||||
if(response.status == 200){
|
||||
window.location.pathname = '/';
|
||||
}else{
|
||||
displayResponseError(await response.json());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Object Instantiation
|
||||
new profileTextEditPrompt("signature");
|
||||
new profileTextEditPrompt("bio", true);
|
||||
new profileImgEditPrompt();
|
||||
new passwordResetPrompt();
|
||||
new deleteAccountPrompt();
|
||||
33
www/js/register.js
Normal file
33
www/js/register.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
//I could make a class like the others but it's so god-damned basic why bother?
|
||||
async function registerPrompt(event){
|
||||
if(!event || event.key == "Enter"){
|
||||
var user = document.querySelector("#register-username").value;
|
||||
var pass = document.querySelector("#register-password").value;
|
||||
var passConfirm = document.querySelector("#register-password-confirm").value;
|
||||
var email = document.querySelector("#register-email").value;
|
||||
|
||||
utils.ajax.register(user, pass, passConfirm, email);
|
||||
}
|
||||
}
|
||||
|
||||
//assign events
|
||||
document.querySelector("#register-username").addEventListener("keydown", registerPrompt)
|
||||
document.querySelector("#register-password").addEventListener("keydown", registerPrompt)
|
||||
document.querySelector("#register-password-confirm").addEventListener("keydown", registerPrompt)
|
||||
document.querySelector("#register-email").addEventListener("keydown", registerPrompt)
|
||||
214
www/js/utils.js
Normal file
214
www/js/utils.js
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
/*Canopy - The next generation of stoner streaming software
|
||||
Copyright (C) 2024 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/>.*/
|
||||
|
||||
class canopyUtils{
|
||||
constructor(){
|
||||
this.ajax = new canopyAjaxUtils();
|
||||
this.ux = new canopyUXUtils();
|
||||
}
|
||||
}
|
||||
|
||||
class canopyUXUtils{
|
||||
constructor(){
|
||||
}
|
||||
|
||||
|
||||
static clickDragger = class{
|
||||
constructor(handle, element, breakPoint){
|
||||
//Pull needed nodes
|
||||
this.handle = document.querySelector(handle);
|
||||
this.element = document.querySelector(element);
|
||||
|
||||
//True while dragging
|
||||
this.dragLock = false;
|
||||
|
||||
//Little hacky but it could be worse :P
|
||||
this.fixWidth = false;
|
||||
|
||||
//Setup our event listeners
|
||||
this.setupInput();
|
||||
}
|
||||
|
||||
setupInput(){
|
||||
this.handle.addEventListener("mousedown", this.startDrag.bind(this));
|
||||
this.element.parentElement.addEventListener("mouseup", this.endDrag.bind(this));
|
||||
this.element.parentElement.addEventListener("mousemove", this.drag.bind(this));
|
||||
}
|
||||
|
||||
startDrag(event){
|
||||
//we are now dragging
|
||||
this.dragLock = true;
|
||||
}
|
||||
|
||||
endDrag(event){
|
||||
//we're no longer dragging
|
||||
this.dragLock = false;
|
||||
|
||||
//if we broke the page we need to fix it
|
||||
if(this.fixWidth){
|
||||
//Pop the element width up just a bit to compensate for the extra pixel
|
||||
this.element.style.width = `${this.calcWidth(this.element.getBoundingClientRect().width + 1)}%`;
|
||||
//if this is true, it no longer needs to be, though it *should* be reset by the drag function by the time it matters anywho :P
|
||||
this.fixWidth = false;
|
||||
}
|
||||
}
|
||||
|
||||
drag(event){
|
||||
|
||||
if(this.dragLock){
|
||||
//get difference between mouse and right edge of element
|
||||
var difference = this.element.getBoundingClientRect().right - event.clientX;
|
||||
//check if we have a scrollbar because we're breaking shit
|
||||
var pageBreak = document.body.scrollWidth - document.body.getBoundingClientRect().width;
|
||||
|
||||
|
||||
//if we're not breaking the page, or we're moving left
|
||||
if(pageBreak <= 0 || event.clientX < this.handle.getBoundingClientRect().left){
|
||||
//Apply difference to width
|
||||
this.element.style.width = `${this.calcWidth(difference)}%`;
|
||||
//If we let go here, the width isn't breaking anything so there's nothing to fix.
|
||||
this.fixWidth = false;
|
||||
}else{
|
||||
//We need to move the element back, but we can't do it all the way while we're still dragging as it will thrash
|
||||
this.element.style.width = `${this.calcWidth(this.element.getBoundingClientRect().width + pageBreak - 1)}%`;
|
||||
//If we stop dragging here, let the endDrag function know to fix the pixel difference used to prevent thrashing
|
||||
this.fixWidth = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
calcWidth(px){
|
||||
return (px / this.element.parentElement.getBoundingClientRect().width) * 100;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class canopyAjaxUtils{
|
||||
|
||||
constructor(){
|
||||
|
||||
}
|
||||
|
||||
//Profile
|
||||
async displayResponseError(body){
|
||||
const err = body.msg;
|
||||
window.alert(`ERROR:\n${err}`);
|
||||
}
|
||||
|
||||
async register(user, pass, passConfirm, email){
|
||||
var response = await fetch(`/api/account/register`,{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(email ? {user, pass, passConfirm, email} : {user, pass, passConfirm})
|
||||
});
|
||||
|
||||
if(response.status == 200){
|
||||
location = "/";
|
||||
}
|
||||
}
|
||||
|
||||
async login(user, pass){
|
||||
var response = await fetch(`/api/account/login`,{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({user, pass})
|
||||
});
|
||||
|
||||
if(response.status == 200){
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
async logout(){
|
||||
var response = await fetch(`/api/account/logout`,{
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
if(response.status == 200){
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
async updateProfile(update){
|
||||
return await fetch(`/api/account/update`,{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(update)
|
||||
});
|
||||
}
|
||||
|
||||
async deleteAccount(pass){
|
||||
return await fetch(`/api/account/delete`,{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({pass})
|
||||
});
|
||||
}
|
||||
|
||||
async newChannel(name, description, thumbnail){
|
||||
var response = await fetch(`/api/channel/register`,{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(thumbnail ? {name, description, thumbnail} : {name, description})
|
||||
});
|
||||
|
||||
if(response.status == 200){
|
||||
location = "/";
|
||||
}
|
||||
}
|
||||
|
||||
async setChannelSetting(chanName, settingsMap){
|
||||
var response = await fetch(`/api/channel/settings`,{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
//Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible...
|
||||
body: JSON.stringify({chanName, settingsMap: Object.fromEntries(settingsMap)})
|
||||
});
|
||||
|
||||
if(response.status == 200){
|
||||
return await response.json();
|
||||
}
|
||||
}
|
||||
|
||||
async deleteChannel(chanName, confirm){
|
||||
var response = await fetch(`/api/channel/delete`,{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({chanName, confirm})
|
||||
});
|
||||
|
||||
if(response.status == 200){
|
||||
location = "/";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const utils = new canopyUtils()
|
||||
BIN
www/video/static.webm
Normal file
BIN
www/video/static.webm
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue