diff --git a/webroot/index.html b/webroot/index.html index 4e38ad891..b2caa0216 100644 --- a/webroot/index.html +++ b/webroot/index.html @@ -6,6 +6,7 @@ href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" /> + @@ -15,88 +16,118 @@ -
-
-
- -
- {{ streamStatus }} {{ viewerCount }} {{ 'viewer' | plural(viewerCount) }}. -
-
+ +
+
+

+ 😈 Owncast +

-
-
-
-
-
- - -
-

{{ message.author }}

-

-
-
+
+
+
+ + Random Username 123
-
-
-
- - - - - -
- -
- + +
+
💬
+
+ +
+
+ +
+ +
+ +
-
-
- +
+ {{ streamStatus }} {{ viewerCount }} {{ 'viewer' | plural(viewerCount) }}. +
+
+ + +
+ +
+
+
+
+ +
+

{{ message.author }}

+

+
+
+
+
+ +
+ + + + + + + + + + +
+ + +
+ +
- +
+ + + +
-
- - + + + + \ No newline at end of file diff --git a/webroot/js/app.js b/webroot/js/app.js index a984b6367..ce45491b9 100644 --- a/webroot/js/app.js +++ b/webroot/js/app.js @@ -1,14 +1,14 @@ function setupApp() { Vue.filter('plural', function (string, count) { if (count === 1) { - return string + return string; } else { - return string + "s" + return string + "s"; } }) window.app = new Vue({ - el: "#app", + el: "#stream-info", data: { streamStatus: "", viewerCount: 0, @@ -32,17 +32,20 @@ function setupApp() { }, methods: { submitChatForm: function (e) { - const message = new Message(this.message) - message.id = uuidv4() - localStorage.author = message.author - const messageJSON = JSON.stringify(message) - window.ws.send(messageJSON) - e.preventDefault() - - this.message.body = "" + const message = new Message(this.message); + message.id = uuidv4(); + localStorage.author = message.author; + const messageJSON = JSON.stringify(message); + window.ws.send(messageJSON); + e.preventDefault(); + + this.message.body = ""; } } }); + + var appMessagingMisc = new Messaging(); + appMessagingMisc.init(); } async function getStatus() { @@ -64,7 +67,7 @@ async function getStatus() { } -var websocketReconnectTimer +var websocketReconnectTimer; function setupWebsocket() { clearTimeout(websocketReconnectTimer) @@ -138,7 +141,7 @@ setupWebsocket() // } function scrollSmoothToBottom(id) { - const div = document.getElementById(id) + const div = document.getElementById(id); $('#' + id).animate({ scrollTop: div.scrollHeight - div.clientHeight }, 500) diff --git a/webroot/js/message.js b/webroot/js/message.js index 36720ed2e..5fc9d704c 100644 --- a/webroot/js/message.js +++ b/webroot/js/message.js @@ -6,8 +6,13 @@ class Message { this.id = model.id } - linkedText() { - return autoLink(this.body, { embed: true }) + addNewlines(str) { + return str.replace(/(?:\r\n|\r|\n)/g, '
'); + + } + formatText() { + var linked = autoLink(this.body, { embed: true }); + return this.addNewlines(linked); } toModel() { @@ -18,4 +23,127 @@ class Message { id: this.id } } +} + + +// convert newlines to
s + +class Messaging { + constructor() { + this.chatDisplayed = false; + this.username = ""; + this.avatarSource = "https://robohash.org/"; + + this.messageCharCount = 0; + this.maxMessageLength = 500; + this.maxMessageBuffer = 20; + + + this.tagChatToggle = document.querySelector("#chat-toggle"); + + this.tagUserInfoDisplay = document.querySelector("#user-info-display"); + this.tagUserInfoChanger = document.querySelector("#user-info-change"); + + this.tagUsernameDisplay = document.querySelector("#username-display"); + this.imgUsernameAvatar = document.querySelector("#username-avatar"); + + this.tagMessageAuthor = document.querySelector("#self-message-author"); + + this.tagMessageFormWarning = document.querySelector("#message-form-warning"); + + this.tagAppContainer = document.querySelector("#app-container"); + + this.inputChangeUserName = document.querySelector("#username-change-input"); + this.btnUpdateUserName = document.querySelector("#button-update-username"); + this.btnCancelUpdateUsername = document.querySelector("#button-cancel-change"); + + this.formMessageInput = document.querySelector("#inputBody"); + + } + init() { + this.tagChatToggle.addEventListener("click", this.handleChatToggle); + this.tagUsernameDisplay.addEventListener("click", this.handleShowChangeNameForm); + + this.btnUpdateUserName.addEventListener("click", this.handleUpdateUsername); + this.btnCancelUpdateUsername.addEventListener("click", this.handleHideChangeNameForm); + + this.inputChangeUserName.addEventListener("keydown", this.handleUsernameKeydown); + this.formMessageInput.addEventListener("keydown", this.handleMessageInputKeydown); + } + + handleChatToggle = () => { + if (this.chatDisplayed) { + this.tagAppContainer.className = "flex no-chat"; + this.chatDisplayed = false; + } else { + this.tagAppContainer.className = "flex"; + this.chatDisplayed = true; + } + } + handleShowChangeNameForm = () => { + this.tagUserInfoDisplay.style.display = "none"; + this.tagUserInfoChanger.style.display = "flex"; + } + handleHideChangeNameForm = () => { + this.tagUserInfoDisplay.style.display = "flex"; + this.tagUserInfoChanger.style.display = "none"; + } + handleUpdateUsername = () => { + var newValue = this.inputChangeUserName.value; + newValue = newValue.trim(); + // do other string cleanup? + + if (newValue) { + this.userName = newValue; + this.inputChangeUserName.value = newValue; + this.tagMessageAuthor.innerText = newValue; + this.tagUsernameDisplay.innerText = newValue; + this.imgUsernameAvatar.src = this.avatarSource + newValue; + } + this.handleHideChangeNameForm(); + } + + handleUsernameKeydown = event => { + if (event.keyCode === 13) { // enter + this.handleUpdateUsername(); + } else if (event.keyCode === 27) { // esc + this.handleHideChangeNameForm(); + } + } + + handleMessageInputKeydown = event => { + var okCodes = [37,38,39,40,16,91,18,46,8]; + var value = this.formMessageInput.value.trim(); + var numCharsLeft = this.maxMessageLength - value.length; + + if (event.keyCode === 13) { // enter + if (!this.prepNewLine) { + // submit() + event.preventDefault(); + // clear out things. + this.formMessageInput.value = ""; + this.tagMessageFormWarning.innerText = ""; + return; + } + this.prepNewLine = false; + } else { + this.prepNewLine = false; + } + if (event.keyCode === 16 || event.keyCode === 17) { // ctrl, shift + this.prepNewLine = true; + } + + if (numCharsLeft <= this.maxMessageBuffer) { + this.tagMessageFormWarning.innerText = numCharsLeft + " chars left"; + if (numCharsLeft <= 0 && !okCodes.includes(event.keyCode)) { + event.preventDefault(); + return; + } + } else { + this.tagMessageFormWarning.innerText = ""; + } + } + + + } \ No newline at end of file diff --git a/webroot/styles/layout.css b/webroot/styles/layout.css new file mode 100644 index 000000000..5ecc164b1 --- /dev/null +++ b/webroot/styles/layout.css @@ -0,0 +1,309 @@ +/* variables */ +:root { + --header-height: 3em; + --right-col-width: 24em; + + --chat-bg-color: rgba(11,0,33,.95); + --header-bg-color: rgba(20,0,40,1); +} + + +body { + font-size: 14px; + background-color: #666; +} +::-webkit-scrollbar { + width: 0px; + background: transparent; +} + +#app-container { + width: 100%; + flex-direction: column; + justify-content: flex-start; + position: relative; + + color: white; +} + +header { + position: fixed; + width: 100%; + height: var(--header-height); + top: 0; + left: 0; + background-color: var(--header-bg-color); + z-index: 10; + flex-direction: row; + justify-content: space-between; +} + +header h1 { + font-size: 1.25em; + font-weight: 100; + letter-spacing: 1.2; + text-transform: uppercase; + color: #ddd; + padding: .5em; + white-space: nowrap; +} + +#chat-toggle { + cursor: pointer; + background-color: #555; + text-align: center; + height: 100%; + width: 3em; + justify-content: center; + align-items: center; +} +#chat-toggle:hover { + background-color: #666; +} + +/* ************************************************8 */ + +#user-options-container { + flex-direction: row; + justify-content: flex-end; + align-items: center; +} + +#user-info-display { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + cursor: pointer; + padding: .5em 1em; +} + +#username-avatar { + height: 1.75em; + width: 1.75em; + margin-right: .5em; + border: 1px solid rgba(255,255,255,.25) +} +#username-display { + font-weight: bold; + font-size: .75em; + color: #516FEB + +} +#user-info-display:hover { + transition: opacity .2s; + opacity: .75; +} + + +#user-info-change { + display: none; + justify-content: flex-end; + align-items: center; + padding: .25em; +} +#username-change-input { + font-size: .75em; +} +#button-update-username { + font-size: .65em; + text-transform: uppercase; + height: 2.5em; +} +#button-cancel-change { + color: rgba(255,255,255,.5); + cursor: pointer; + height: 2.5em; + font-size: .65em; +} +.user-btn { + margin: 0 .25em; +} + +/* ************************************************8 */ + +#main-content-container { + width: 100%; + flex-direction: row; + position: relative; + margin-top: var(--header-height); +} + +.main-cols { + flex-direction: column; + justify-content: flex-start; + position: relative; +} + +.left-col { + width: calc(100vw - var(--right-col-width)); +} + +/* ************************************************8 */ + +#video-container { + width: 100%; + height: auto; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; +} +#video-container video { + width: 100%; + display: block; +} + +#stream-info { + padding: .5em; + text-align: center; + font-family: monospace; + font-size: .75em; + background-color: rgba(0,0,0,.5); + border-bottom: 1px solid black; +} + +/* ************************************************8 */ + + +#chat-container { + position: fixed; + z-index: 9; + right: 0; + height: 100%; + width: var(--right-col-width); + background-color: var(--chat-bg-color); + height: calc(100vh - var(--header-height)); + overflow: hidden; + display: flex; + flex-direction: column; + justify-content: flex-end; +} + +#messages-container { + overflow: auto; + padding: 1em 0; +} +#message-input-container { + width: 100%; + border-top: 1px solid #eee; + padding: 1em; + background-color: #334; +} + +#message-form { + flex-direction: column; + align-items: flex-end; + margin-bottom: 0; +} +#message-form-actions { + flex-direction: row; + justify-content: space-between; + align-items: center; + width: 100%; +} +#message-form-warning { + font-size: .75em; + color: red; +} + +/* ************************************************8 */ + +.message { + padding: .85em; + align-items: flex-start; +} +.message-avatar { + height: 2.5em; + width: 2.5em; + margin-right: .75em; + background-color: rgba(0,0,0, .75); +} +.message-content { + font-size: .85em; +} +.message-content a { + color: #6699cc; +} +.message-content a:hover { + text-decoration: underline; +} +.message-author { + font-weight: 600; +} +.message-text { + color: #ccc; + font-weight: 100; +} +/* ************************************************8 */ + + +.no-chat .left-col { + width: 100vw; +} +.no-chat .right-col { + display: none; +} + +.no-chat #chat-toggle { + opacity: .5; +} + +/* ************************************************8 */ + +@media screen and (max-width: 860px) { + :root { + --right-col-width: 20em; + } + + #chat-container { + width: var(--right-col-width); + } + .left-col { + width: calc(100vw - var(--right-col-width)); + } +} + +@media screen and (max-width: 640px ) and (orientation: portrait) { + #main-content-container { + flex-direction: column; + justify-content: space-between; + height: calc(100vh - var(--header-height)); + } + + .main-cols { + width: 100vw; + } + .left-col { + flex-direction: column; + justify-content: stretch; + } + .right-col { + overflow: hidden; + } + + + #info { + display: none; + overflow: auto; + height: auto; + } + + #chat-container { + width: 100%; + height: 100%; + position: relative; + height: auto; + } + + .no-chat .left-col { + height: 100%; + } + .no-chat .right-col { + display: none; + } + .no-chat #info { + display: block; + } + +} \ No newline at end of file diff --git a/webroot/vendor/autolink.js b/webroot/vendor/autolink.js index 796b001f7..66ba0e8ec 100644 --- a/webroot/vendor/autolink.js +++ b/webroot/vendor/autolink.js @@ -113,7 +113,7 @@ AutoLink.prototype = { var text = this.options.removeHTTP ? removeHTTP(match) : match return ( p1 + - '