Fix + update external modal (#1106)

* Update external action modal. Uses Micromodal. Closes #1020

* refactor modal handling to isolate loading in modal component

* modal style cleanup

* Remove log

Co-authored-by: Ginger Wong <omqmail@gmail.com>
This commit is contained in:
Gabe Kangas 2021-06-20 17:23:39 -07:00 committed by GitHub
parent c848c029d5
commit 57674206b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 341 additions and 194 deletions

View File

@ -1240,6 +1240,11 @@
"picomatch": "^2.0.5" "picomatch": "^2.0.5"
} }
}, },
"micromodal": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/micromodal/-/micromodal-0.4.6.tgz",
"integrity": "sha512-2VDso2a22jWPpqwuWT/4RomVpoU3Bl9qF9D01xzwlNp5UVsImeA0gY4nSpF44vqcQtQOtkiMUV9EZkAJSRxBsg=="
},
"min-document": { "min-document": {
"version": "2.19.0", "version": "2.19.0",
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",

View File

@ -9,6 +9,7 @@
"@videojs/themes": "^1.0.1", "@videojs/themes": "^1.0.1",
"htm": "^3.0.4", "htm": "^3.0.4",
"mark.js": "^8.11.1", "mark.js": "^8.11.1",
"micromodal": "^0.4.6",
"preact": "10.5.10", "preact": "10.5.10",
"tailwindcss": "^1.9.6", "tailwindcss": "^1.9.6",
"video.js": "7.12.3" "video.js": "7.12.3"
@ -28,7 +29,8 @@
"htm", "htm",
"preact", "preact",
"mark.js/dist/mark.es6.min.js", "mark.js/dist/mark.es6.min.js",
"tailwindcss/dist/tailwind.min.css" "tailwindcss/dist/tailwind.min.css",
"micromodal/dist/micromodal.min.js"
], ],
"alias": { "alias": {
"video.js": "video.js/core.js" "video.js": "video.js/core.js"

View File

@ -1,9 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<head>
<title>Owncast</title> <title>Owncast</title>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<link rel="apple-touch-icon" sizes="57x57" href="/img/favicon/apple-icon-57x57.png"> <link rel="apple-touch-icon" sizes="57x57" href="/img/favicon/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/img/favicon/apple-icon-60x60.png"> <link rel="apple-touch-icon" sizes="60x60" href="/img/favicon/apple-icon-60x60.png">
@ -26,7 +27,7 @@
<link href="/js/web_modules/tailwindcss/dist/tailwind.min.css" rel="stylesheet" /> <link href="/js/web_modules/tailwindcss/dist/tailwind.min.css" rel="stylesheet" />
<link href="/js/web_modules/videojs/video-js.min.css" rel="stylesheet"/> <link href="/js/web_modules/videojs/video-js.min.css" rel="stylesheet" />
<link href="/js/web_modules/@videojs/themes/fantasy/index.css" rel="stylesheet" /> <link href="/js/web_modules/@videojs/themes/fantasy/index.css" rel="stylesheet" />
<link href="./styles/video.css" rel="stylesheet" /> <link href="./styles/video.css" rel="stylesheet" />
@ -40,37 +41,42 @@
If you wish to re-generate this list, run the following shell command If you wish to re-generate this list, run the following shell command
(assuming a linux or unix-ish system): (assuming a linux or unix-ish system):
find webroot | grep -E '\.js$' | sed -E 's|webroot(.*)|<script type="preload" src="\1"></script>|' find webroot | grep -E '\.js$' | sed -E 's|webroot(.*)|<script type="preload" src="\1"></script>|'
Don't load/preload chat-only or video-only. Don't load/preload app-standalone-chat.js or app-video-only.js.
--> -->
<script type="preload" src="/js/app.js"></script> <script type="preload" src="/js/components/platform-logos-list.js"></script>
<script type="preload" src="/js/components/chat/chat-input.js"></script> <script type="preload" src="/js/components/chat/chat-input.js"></script>
<script type="preload" src="/js/components/chat/message.js"></script>
<script type="preload" src="/js/components/chat/content-editable.js"></script>
<script type="preload" src="/js/components/chat/chat.js"></script> <script type="preload" src="/js/components/chat/chat.js"></script>
<script type="preload" src="/js/components/chat/chat-message-view.js"></script> <script type="preload" src="/js/components/chat/chat-message-view.js"></script>
<script type="preload" src="/js/components/chat/content-editable.js"></script>
<script type="preload" src="/js/components/chat/message.js"></script>
<script type="preload" src="/js/components/chat/username.js"></script> <script type="preload" src="/js/components/chat/username.js"></script>
<script type="preload" src="/js/components/platform-logos-list.js"></script> <script type="preload" src="/js/components/external-action-modal.js"></script>
<script type="preload" src="/js/components/player.js"></script> <script type="preload" src="/js/components/player.js"></script>
<script type="preload" src="/js/components/video-poster.js"></script> <script type="preload" src="/js/components/video-poster.js"></script>
<script type="preload" src="/js/utils/chat.js"></script> <script type="preload" src="/js/app.js"></script>
<script type="preload" src="/js/utils/constants.js"></script> <script type="preload" src="/js/web_modules/preact.js"></script>
<script type="preload" src="/js/utils/helpers.js"></script> <script type="preload" src="/js/web_modules/micromodal/dist/micromodal.min.js"></script>
<script type="preload" src="/js/utils/user-colors.js"></script> <script type="preload" src="/js/web_modules/common/core-a6c34bea.js"></script>
<script type="preload" src="/js/utils/websocket.js"></script>
<script type="preload" src="/js/web_modules/common/_commonjsHelpers-37fa8da4.js"></script> <script type="preload" src="/js/web_modules/common/_commonjsHelpers-37fa8da4.js"></script>
<script type="preload" src="/js/web_modules/common/core-d14f1e1c.js"></script> <script type="preload" src="/js/web_modules/common/core-d14f1e1c.js"></script>
<script type="preload" src="/js/web_modules/common/core-fed3ccd8.js"></script> <script type="preload" src="/js/web_modules/common/core-fed3ccd8.js"></script>
<script type="preload" src="/js/web_modules/htm.js"></script> <script type="preload" src="/js/web_modules/common/core-f87370e0.js"></script>
<script type="preload" src="/js/web_modules/@joeattardi/emoji-button.js"></script> <script type="preload" src="/js/web_modules/common/core-d20ff47c.js"></script>
<script type="preload" src="/js/web_modules/markjs/dist/mark.es6.min.js"></script>
<script type="preload" src="/js/web_modules/preact.js"></script>
<script type="preload" src="/js/web_modules/@videojs/http-streaming/dist/videojs-http-streaming.min.js"></script> <script type="preload" src="/js/web_modules/@videojs/http-streaming/dist/videojs-http-streaming.min.js"></script>
<script type="preload" src="/js/web_modules/markjs/dist/mark.es6.min.js"></script>
<script type="preload" src="/js/web_modules/@joeattardi/emoji-button.js"></script>
<script type="preload" src="/js/web_modules/htm.js"></script>
<script type="preload" src="/js/web_modules/videojs/core.js"></script> <script type="preload" src="/js/web_modules/videojs/core.js"></script>
<script type="preload" src="/js/utils/helpers.js"></script>
<script type="preload" src="/js/utils/user-colors.js"></script>
<script type="preload" src="/js/utils/constants.js"></script>
<script type="preload" src="/js/utils/chat.js"></script>
<script type="preload" src="/js/utils/websocket.js"></script>
</head> </head>
<body class="scrollbar-hidden bg-gray-300 text-gray-800"> <body class="scrollbar-hidden bg-gray-300 text-gray-800">
<div id="app"> <div id="app">
<div id="loading-logo-container"> <div id="loading-logo-container">
<img id="loading-logo" src="/logo"> <img id="loading-logo" src="/logo">
@ -105,11 +111,13 @@
</style> </style>
<div class="noscript"> <div class="noscript">
<img src="/logo" /> <img src="/logo" />
<br/> <br />
<p> <p>
This <a href="https://owncast.online" rel="noopener noreferrer" target="_blank">Owncast</a> stream requires Javascript to play. This <a href="https://owncast.online" rel="noopener noreferrer" target="_blank">Owncast</a> stream requires
Javascript to play.
</p> </p>
</div> </div>
</noscript> </noscript>
</body> </body>
</html> </html>

View File

@ -424,9 +424,7 @@ export default class App extends Component {
} }
handleKeyPressed(e) { handleKeyPressed(e) {
if (e.code === 'Escape' && this.state.externalAction !== null) { if (
this.closeExternalActionModal();
} else if (
e.target !== document.getElementById('message-input') && e.target !== document.getElementById('message-input') &&
e.target !== document.getElementById('username-change-input') && e.target !== document.getElementById('username-change-input') &&
e.target !== document.getElementsByClassName('emoji-picker__search')[0] && e.target !== document.getElementsByClassName('emoji-picker__search')[0] &&
@ -456,9 +454,8 @@ export default class App extends Component {
} }
} }
displayExternalAction(index) { displayExternalAction(action) {
const { configData, username } = this.state; const { username } = this.state;
const action = configData.externalActions[index];
if (!action) { if (!action) {
return; return;
} }
@ -475,13 +472,13 @@ export default class App extends Component {
win.focus(); win.focus();
return; return;
} }
action.url = fullUrl;
this.setState({ this.setState({
externalAction: action, externalAction: {
...action,
url: fullUrl,
},
}); });
} }
closeExternalActionModal() { closeExternalActionModal() {
this.setState({ this.setState({
externalAction: null, externalAction: null,
@ -558,32 +555,25 @@ export default class App extends Component {
? null ? null
: html` <${VideoPoster} offlineImage=${logo} active=${streamOnline} /> `; : html` <${VideoPoster} offlineImage=${logo} active=${streamOnline} /> `;
// modal buttons
const externalActionButtons = const externalActionButtons =
externalActions && externalActions.length > 0 externalActions &&
? html`<div html`<div
id="external-actions-container" id="external-actions-container"
class="flex flex-row align-center" class="flex flex-row align-center"
> >
${externalActions.map( ${externalActions.map(
function (action, index) { function (action) {
return html`<${ExternalActionButton} return html`<${ExternalActionButton}
onClick=${this.displayExternalAction} onClick=${this.displayExternalAction}
action=${action} action=${action}
index=${index}
/>`; />`;
}.bind(this) }.bind(this)
)} )}
</div>` </div>`;
: null;
const externalActionModal = externalAction // modal component
? html`<${ExternalActionModal} const externalActionModal = externalAction && html`<${ExternalActionModal} action=${externalAction} onClose=${this.closeExternalActionModal} />`;
title=${this.state.externalAction.description ||
this.state.externalAction.title}
url=${this.state.externalAction.url}
onClose=${this.closeExternalActionModal}
/>`
: null;
return html` return html`
<div <div

View File

@ -1,77 +1,79 @@
import { h } from '/js/web_modules/preact.js'; import { h, Component } from '/js/web_modules/preact.js';
import htm from '/js/web_modules/htm.js'; import htm from '/js/web_modules/htm.js';
import MicroModal from '/js/web_modules/micromodal/dist/micromodal.min.js';
const html = htm.bind(h); const html = htm.bind(h);
export default function ExternalActionModal({ url, title, onClose }) { export default class ExternalActionModal extends Component {
const loading = 'background:url(/img/loading.gif) center center no-repeat;'; constructor(props) {
super(props);
this.state = {
iframeLoaded: false,
};
function loaded() { this.setIframeLoaded = this.setIframeLoaded.bind(this);
document.querySelector('#external-modal-iframe').style = ''; }
componentDidMount() {
// initalize and display Micromodal on mount
try {
MicroModal.init({
awaitCloseAnimation: false,
awaitOpenAnimation: true, // if using css animations to open the modal. This allows it to wait for the animation to finish before focusing on an element inside the modal.
});
MicroModal.show('external-actions-modal', {
onClose: this.props.onClose,
});
} catch (e) {
console.log("micromodal error: ", e);
}
} }
setIframeLoaded() {
this.setState({
iframeLoaded: true,
});
}
render() {
const { action } = this.props;
const { url, title, description } = action;
const { iframeLoaded } = this.state;
const iframeStyle = iframeLoaded ? null : {
backgroundImage: 'url(/img/loading.gif)',
};
return html` return html`
<div class="fixed inset-0 overflow-y-auto" style="z-index: 9999"> <div class="modal micromodal-slide" id="external-actions-modal" aria-hidden="true">
<div <div class="modal__overlay" tabindex="-1" data-micromodal-close>
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0" <div id="modal-container" class="modal__container rounded-md" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
> <header id="modal-header" class="modal__header flex flex-row justify-between items-center bg-gray-300 p-3 rounded-t-md">
<div class="fixed inset-0 transition-opacity" aria-hidden="true"> <h2 class="modal__title text-indigo-600 font-semibold">
<div ${title || description}
onClick=${() => onClose()} </h2>
class="absolute inset-0 bg-gray-900 bg-opacity-75" <button class="modal__close" aria-label="Close modal" data-micromodal-close></button>
></div> </header>
</div> <div id="modal-content-content" class="modal-content-content">
<div id="modal-content" class="modal__content text-gray-600 rounded-b-md overflow-y-auto overflow-x-hidden">
<!-- This element is to trick the browser into centering the modal contents. -->
<span
class="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true"
>&#8203;</span
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:align-middle w-screen md:max-w-2xl lg:max-w-2xl"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline"
>
<div class="bg-white ">
<div
class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex justify-between items-center"
>
<h3 class="font-bold hidden md:block">${title}</h3>
<span class="" onclick=${onClose}>
<svg
class="h-12 w-12 fill-current text-grey hover:text-grey-darkest"
role="button"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<title>Close</title>
<path
d="M14.348 14.849a1.2 1.2 0 0 1-1.697 0L10 11.819l-2.651 3.029a1.2 1.2 0 1 1-1.697-1.697l2.758-3.15-2.759-3.152a1.2 1.2 0 1 1 1.697-1.697L10 8.183l2.651-3.031a1.2 1.2 0 1 1 1.697 1.697l-2.758 3.152 2.758 3.15a1.2 1.2 0 0 1 0 1.698z"
/>
</svg>
</span>
</div>
<iframe <iframe
id="external-modal-iframe" id="external-modal-iframe"
style=${`${loading}`} style=${iframeStyle}
class="bg-gray-100 bg-center bg-no-repeat"
width="100%" width="100%"
allowpaymentrequest="true" allowpaymentrequest="true"
allowfullscreen="false" allowfullscreen="false"
sandbox="allow-same-origin allow-scripts allow-popups allow-forms" sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
src=${url} src=${url}
onload=${loaded} onload=${this.setIframeLoaded}
/> />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
`; `;
}
} }
export function ExternalActionButton({ index, action, onClick }) { export function ExternalActionButton({ action, onClick }) {
const { title, icon, color = undefined } = action; const { title, icon, color = undefined } = action;
const logo = const logo =
icon && icon &&
@ -79,11 +81,10 @@ export function ExternalActionButton({ index, action, onClick }) {
<span class="external-action-icon"><img src=${icon} alt="" /></span> <span class="external-action-icon"><img src=${icon} alt="" /></span>
`; `;
const bgcolor = color && { backgroundColor: `${color}` }; const bgcolor = color && { backgroundColor: `${color}` };
const handleClick = () => onClick(index); const handleClick = () => onClick(action);
return html` return html`
<button <button
class="external-action-button rounded-sm flex flex-row justify-center items-center overflow-hidden bg-gray-800" class="external-action-button rounded-sm flex flex-row justify-center items-center overflow-hidden bg-gray-800"
data-index=${index}
onClick=${handleClick} onClick=${handleClick}
style=${bgcolor} style=${bgcolor}
> >

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,7 @@
"@videojs/themes/fantasy/index.css": "./@videojs/themes/fantasy/index.css", "@videojs/themes/fantasy/index.css": "./@videojs/themes/fantasy/index.css",
"htm": "./htm.js", "htm": "./htm.js",
"mark.js/dist/mark.es6.min.js": "./markjs/dist/mark.es6.min.js", "mark.js/dist/mark.es6.min.js": "./markjs/dist/mark.es6.min.js",
"micromodal/dist/micromodal.min.js": "./micromodal/dist/micromodal.min.js",
"preact": "./preact.js", "preact": "./preact.js",
"tailwindcss/dist/tailwind.min.css": "./tailwindcss/dist/tailwind.min.css", "tailwindcss/dist/tailwind.min.css": "./tailwindcss/dist/tailwind.min.css",
"video.js": "./video.js/core.js", "video.js": "./video.js/core.js",

File diff suppressed because one or more lines are too long

View File

@ -278,6 +278,139 @@ header {
} }
} }
#external-modal-iframe { #external-modal-iframe {
height: 70vh; height: 70vh;
} }
/**************************
Basic Modal Styles
**************************/
/*
External modal styling for use with Micromodal from https://gist.github.com/ghosh/4f94cf497d7090359a5c9f81caf60699
See external-action-modal.js
*/
.modal {
z-index: 100;
}
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.75);
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
}
.modal__container {
background-color: transparent;
padding: 0px;
max-width: 740px;
height: 75vh;
width: 50%;
border-radius: 0px;
overflow: hidden;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
cursor: pointer;
margin: 0px;
padding: 0px;
outline: none;
cursor: pointer !important;
}
.modal__close:before { content: "\2715"; font-size: 1.25rem; }
@supports (display: flex) {
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
height:initial;
}
.modal__title {
position: static;
}
.modal__close {
position: static;
}
}
/**************************
Modal Animation Style
**************************/
@keyframes mmfadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes mmfadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes mmslideIn {
from { transform: translateY(15%); }
to { transform: translateY(0); }
}
@keyframes mmslideOut {
from { transform: translateY(0); }
to { transform: translateY(-10%); }
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
/* Miromodal mobile styling */
@media only screen and (min-device-width : 600px) and (max-device-width : 480px) {
.modal__container {
width: 90% !important;
min-width: 90% !important;
}
@supports (display: flex) {
.modal__container {
width: 90% !important;
min-width: 90% !important;
height: 85vh;
}
}
.modal__content {
-webkit-overflow-scrolling: touch;
}
}