/* tslint:disable:max-line-length */
"use strict";
import React, { Component } from "react";
import ReactDOM from "react-dom";
import knownApps from "../../lib/knownApps";
import { getNextEllipsis } from "../../lib/utils";
import { Message, Receiver } from "../../types";
import { ReceiverSelectorMediaType } from "../../background/receiverSelector";
const _ = browser.i18n.getMessage;
// macOS styles
browser.runtime.getPlatformInfo()
.then(platformInfo => {
if (platformInfo.os === "mac") {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = "styles/mac.css";
document.head.appendChild(link);
}
});
interface PopupAppState {
receivers: Receiver[];
mediaType: ReceiverSelectorMediaType;
availableMediaTypes: ReceiverSelectorMediaType;
isLoading: boolean;
filePath?: string;
requestedAppId?: string;
}
class PopupApp extends Component<{}, PopupAppState> {
private port?: browser.runtime.Port;
private win?: browser.windows.Window;
private defaultMediaType?: ReceiverSelectorMediaType;
constructor (props: {}) {
super(props);
this.state = {
receivers: []
, mediaType: ReceiverSelectorMediaType.App
, availableMediaTypes: ReceiverSelectorMediaType.App
, isLoading: false
};
// Store window ref
browser.windows.getCurrent().then(win => {
this.win = win;
});
this.onSelectChange = this.onSelectChange.bind(this);
this.onCast = this.onCast.bind(this);
this.onStop = this.onStop.bind(this);
}
public componentDidMount () {
this.port = browser.runtime.connect({
name: "popup"
});
this.port.onMessage.addListener((message: Message) => {
switch (message.subject) {
case "popup:/sendRequestedAppId": {
this.setState({
requestedAppId: message.data.requestedAppId
});
break;
}
case "popup:/populateReceiverList": {
const { receivers, availableMediaTypes, defaultMediaType }
: { receivers: Receiver[]
, availableMediaTypes: ReceiverSelectorMediaType
, defaultMediaType: ReceiverSelectorMediaType } = message.data;
this.defaultMediaType = defaultMediaType;
this.setState({
receivers: message.data.receivers
, mediaType: this.defaultMediaType
, availableMediaTypes: message.data.availableMediaTypes
});
break;
}
case "popup:/close": {
window.close();
break;
}
}
});
}
public componentDidUpdate () {
setTimeout(() => {
if (this.win?.id === undefined) {
return;
}
// Fit window to content height
const frameHeight = window.outerHeight - window.innerHeight;
const windowHeight = document.body.clientHeight + frameHeight;
browser.windows.update(this.win.id, {
height: windowHeight
});
}, 1);
}
public render () {
let truncatedFileName: string;
if (this.state.filePath) {
const filePath = this.state.filePath;
const fileName = filePath.substring(filePath.lastIndexOf("/") + 1);
truncatedFileName = fileName.length > 12
? `${fileName.substring(0, 12)}...`
: fileName;
}
const canCast = !!(this.state.availableMediaTypes
&& this.state.availableMediaTypes & this.state.mediaType);
return (
{ _("popupMediaSelectCastLabel") }
{ _("popupMediaSelectToLabel") }
);
}
private onCast (receiver: Receiver) {
this.setState({
isLoading: true
});
this.port?.postMessage({
subject: "receiverSelector:/selected"
, data: {
receiver
, mediaType: this.state.mediaType
, filePath: this.state.filePath
}
});
}
private onStop (receiver: Receiver) {
this.port?.postMessage({
subject: "receiverSelector:/stop"
, data: { receiver }
});
}
private onSelectChange (ev: React.ChangeEvent) {
const mediaType = parseInt(ev.target.value);
if (mediaType === ReceiverSelectorMediaType.File) {
const fileUrl = window.prompt();
if (fileUrl) {
this.setState({
mediaType
, filePath: fileUrl
});
return;
}
// Set media type to default if failed to set filePath
if (this.defaultMediaType) {
this.setState({
mediaType: this.defaultMediaType
});
}
} else {
this.setState({
mediaType
});
}
this.setState({
filePath: undefined
});
}
}
interface ReceiverEntryProps {
receiver: Receiver;
isLoading: boolean;
canCast: boolean;
onCast (receiver: Receiver): void;
onStop (receiver: Receiver): void;
}
interface ReceiverEntryState {
ellipsis: string;
isLoading: boolean;
showAlternateAction: boolean;
}
class ReceiverEntry extends Component {
constructor (props: ReceiverEntryProps) {
super(props);
this.state = {
ellipsis: ""
, isLoading: false
, showAlternateAction: false
};
const handleActionKeyEvents = (ev: KeyboardEvent) => {
if (ev.key === "Alt" || ev.key === "Shift") {
this.setState({
// Only enable on keydown, otherwise disable
showAlternateAction: ev.type === "keydown"
});
}
};
window.addEventListener("keydown", handleActionKeyEvents);
window.addEventListener("keyup", handleActionKeyEvents);
window.addEventListener("blur", () => {
this.setState({
showAlternateAction: false
});
});
this.handleCast = this.handleCast.bind(this);
}
public render () {
if (!this.props.receiver.status) {
return;
}
const { application } = this.props.receiver.status;
return (
{ this.props.receiver.friendlyName }
{ application.isIdleScreen
? `${this.props.receiver.host}:${this.props.receiver.port}`
: application.statusText }
);
}
private handleCast () {
if (!this.props.receiver.status) {
return;
}
const { application } = this.props.receiver.status;
if (!application.isIdleScreen && this.state.showAlternateAction) {
this.props.onStop(this.props.receiver);
} else {
this.props.onCast(this.props.receiver);
this.setState({
isLoading: true
});
setInterval(() => {
this.setState(state => ({
ellipsis: getNextEllipsis(state.ellipsis)
}));
}, 500);
}
}
}
// Render after CSS has loaded
window.addEventListener("load", () => {
ReactDOM.render(
, document.querySelector("#root"));
});