Gek/external actions (#827)

* WIP External actions modal frontend

* Add external action links

* Allow modal to show/hide and use a dynamic url

* Use external link object instead of just url for state

* add style and placement to external action buttons

* reformat and simplify tag list style as not to conflict with action buttons and make them look less actionable since they're not

* fix bug to open modal

* have Esc key close modal

* fix style on modal

* make modal bg darker

* close modal when you click outside of it

* fix zindex

* Add support for external action icons and colors

* Some external action modal sizing + loading spinner

Co-authored-by: Ginger Wong <omqmail@gmail.com>
This commit is contained in:
Gabe Kangas
2021-03-15 15:32:52 -07:00
committed by GitHub
parent 84f74f0353
commit 3fb80554ef
11 changed files with 288 additions and 37 deletions

View File

@@ -13,6 +13,9 @@ import {
hasTouchScreen,
getOrientation,
} from './utils/helpers.js';
import ExternalActionModal, {
ExternalActionButton,
} from './components/external-action-modal.js';
import {
addNewlines,
@@ -73,6 +76,8 @@ export default class App extends Component {
windowWidth: window.innerWidth,
windowHeight: window.innerHeight,
orientation: getOrientation(this.hasTouchScreen),
externalAction: null,
};
// timers
@@ -96,7 +101,10 @@ export default class App extends Component {
this.disableChatInput = this.disableChatInput.bind(this);
this.setCurrentStreamDuration = this.setCurrentStreamDuration.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleKeyPressed = this.handleKeyPressed.bind(this);
this.displayExternalAction = this.displayExternalAction.bind(this);
this.closeExternalActionModal = this.closeExternalActionModal.bind(this);
// player events
this.handlePlayerReady = this.handlePlayerReady.bind(this);
@@ -119,6 +127,7 @@ export default class App extends Component {
if (this.hasTouchScreen) {
window.addEventListener('orientationchange', this.handleWindowResize);
}
window.addEventListener('keydown', this.handleKeyDown);
window.addEventListener('keypress', this.handleKeyPressed);
this.player = new OwncastPlayer();
this.player.setupPlayerCallbacks({
@@ -140,6 +149,7 @@ export default class App extends Component {
window.removeEventListener('resize', this.handleWindowResize);
window.removeEventListener('blur', this.handleWindowBlur);
window.removeEventListener('focus', this.handleWindowFocus);
window.removeEventListener('keydown', this.handleKeyDown);
window.removeEventListener('keypress', this.handleKeyPressed);
if (this.hasTouchScreen) {
window.removeEventListener('orientationchange', this.handleWindowResize);
@@ -386,6 +396,12 @@ export default class App extends Component {
}
}
handleKeyDown(e) {
if (e.code === 'Escape' && this.state.externalAction !== null) {
this.closeExternalActionModal();
}
}
handleKeyPressed(e) {
if (
e.code === 'Space' &&
@@ -396,6 +412,35 @@ export default class App extends Component {
}
}
displayExternalAction(index) {
const { configData, username } = this.state;
const action = configData.externalActions[index];
if (!action) {
return;
}
const { url: actionUrl, openExternally } = action || {};
let url = new URL(actionUrl);
// Append url and username to params so the link knows where we came from and who we are.
url.searchParams.append('username', username);
url.searchParams.append('instance', window.location);
if (openExternally) {
var win = window.open(url.toString(), '_blank');
win.focus();
return;
}
this.setState({
externalAction: action,
});
}
closeExternalActionModal() {
this.setState({
externalAction: null,
});
}
render(props, state) {
const {
chatInputEnabled,
@@ -413,6 +458,7 @@ export default class App extends Component {
websocket,
windowHeight,
windowWidth,
externalAction,
} = state;
const {
@@ -424,23 +470,12 @@ export default class App extends Component {
name,
extraPageContent,
chatDisabled,
externalActions,
} = configData;
const bgUserLogo = { backgroundImage: `url(${logo})` };
const tagList =
tags !== null && tags.length > 0
? tags.map(
(tag, index) => html`
<li
key="tag${index}"
class="tag rounded-sm text-gray-100 bg-gray-700 text-xs uppercase mr-3 mb-2 p-2 whitespace-no-wrap"
>
${tag}
</li>
`
)
: null;
const tagList = tags !== null && tags.length > 0 && tags.join(' #');
const viewerCountMessage =
streamOnline && viewerCount > 0
@@ -470,6 +505,32 @@ export default class App extends Component {
? null
: html` <${VideoPoster} offlineImage=${logo} active=${streamOnline} /> `;
const externalActionButtons =
externalActions &&
html`<div
id="external-actions-container"
class="flex flex-row align-center"
>
${externalActions.map(
function (action, index) {
return html`<${ExternalActionButton}
onClick=${this.displayExternalAction}
action=${action}
index=${index}
/>`;
}.bind(this)
)}
</div>`;
const externalActionModal = externalAction
? html`<${ExternalActionModal}
title=${this.state.externalAction.description ||
this.state.externalAction.title}
url=${this.state.externalAction.url}
onClose=${this.closeExternalActionModal}
/>`
: null;
return html`
<div
id="app-container"
@@ -555,6 +616,7 @@ export default class App extends Component {
<div
class="user-content-header border-b border-gray-500 border-solid"
>
${externalActionButtons}
<h2 class="font-semibold text-5xl">
<span class="streamer-name text-indigo-600">${name}</span>
</h2>
@@ -567,9 +629,9 @@ export default class App extends Component {
class="stream-summary my-4"
dangerouslySetInnerHTML=${{ __html: summary }}
></div>
<ul id="tag-list" class="tag-list flex flex-row flex-wrap my-4">
${tagList}
</ul>
<div id="tag-list" class="tag-list text-gray-600 mb-3">
${tagList && `#${tagList}`}
</div>
</div>
</div>
<div
@@ -592,6 +654,7 @@ export default class App extends Component {
chatInputEnabled=${chatInputEnabled && !chatDisabled}
instanceTitle=${name}
/>
${externalActionModal}
</div>
`;
}