Add bridge downloads to options page

This commit is contained in:
hensm
2019-02-12 09:06:57 +00:00
parent da17c6df0d
commit d4d55ea59e
7 changed files with 310 additions and 86 deletions

View File

@@ -21,14 +21,17 @@
, "optionsBridgeLoading": {
"message": "Loading bridge info..."
}
, "optionsBridgeFoundStatusText": {
, "optionsBridgeFoundStatusTitle": {
"message": "Bridge found"
}
, "optionsBridgeIssueStatusText": {
, "optionsBridgeIssueStatusTitle": {
"message": "Bridge issue"
}
, "optionsBridgeNotFoundStatusTitle": {
"message": "Bridge not found"
}
, "optionsBridgeNotFoundStatusText": {
"message": "Bridge not found. Try downloading and installing the latest version."
"message": "Try downloading and installing the latest version."
}
, "optionsBridgeInfo": {
"message": "Bridge info:"
@@ -66,6 +69,18 @@
, "optionsBridgeNoAction": {
"message": "No action needed."
}
, "optionsBridgeDownloadsGet": {
"message": "Fetch downloads"
}
, "optionsBridgeDownloadsLoading": {
"message": "Fetching downloads$1"
}
, "optionsBridgeDownloadsGetFailed": {
"message": "Failed to fetch downloads"
}
, "optionsBridgeDownloadsTitle": {
"message": "Bridge downloads"
}
, "optionsMediaCategoryName": {
"message": "Media casting"

10
ext/src/lib/utils.js Normal file
View File

@@ -0,0 +1,10 @@
"use strict";
export function getNextEllipsis (ellipsis) {
return do {
if (ellipsis === "") ".";
else if (ellipsis === ".") "..";
else if (ellipsis === "..") "...";
else if (ellipsis === "...") "";
};
}

View File

@@ -31,7 +31,9 @@
"page": "options/index.html"
}
, "permissions": [
"menus"
"downloads"
, "downloads.open"
, "menus"
, "nativeMessaging"
, "storage"
, "webNavigation"

View File

@@ -1,7 +1,45 @@
import React, { Component } from "react";
import { getNextEllipsis } from "../lib/utils";
const _ = browser.i18n.getMessage;
const ENDPOINT_URL = "https://api.github.com/repos/hensm/fx_cast/releases/14720978";
async function downloadApp (info, platform) {
const download = browser.downloads.download({
filename: info[platform].name
, url: info[platform].url
});
}
const BridgeDownloads = (props) => (
<div className="bridge-downloads">
<button className="bridge-downloads__download
bridge-downloads__win"
disabled
onClick={ () => downloadApp(props.info, "win") }>
Windows
</button>
<button className="bridge-downloads__download
bridge-downloads__mac"
onClick={ () => downloadApp(props.info, "mac") }>
macOS
</button>
<div className="bridge-downloads__linux">
<button className="bridge-downloads__download"
onClick={ () => downloadApp(props.info, "deb") }>
Linux (deb)
</button>
<button className="bridge-downloads__download"
onClick={ () => downloadApp(props.info, "rpm") }>
Linux (rpm)
</button>
</div>
</div>
);
const BridgeStats = (props) => (
<table className="bridge__stats">
<tr>
@@ -49,54 +87,176 @@ const BridgeStats = (props) => (
</table>
);
export default (props) => (
<div className="bridge">
{ do {
if (props.loading) {
<div className="bridge__loading">
{ _("optionsBridgeLoading") }
<progress></progress>
</div>
} else {
const infoClasses = `bridge__info ${props.info
? "bridge__info--found"
: "bridge__info--not-found"}`;
export default class Bridge extends Component {
constructor (props) {
super(props);
const [ statusIcon, statusText ] = do {
if (!props.info) {
[ "assets/icons8-cancel-120.png"
, _("optionsBridgeNotFoundStatusText") ]
} else {
if (props.info.isVersionCompatible) {
[ "assets/icons8-ok-120.png"
, _("optionsBridgeFoundStatusText") ]
} else {
[ "assets/icons8-warn-120.png"
, _("optionsBridgeIssueStatusText") ]
}
}
this.onGetDownloads = this.onGetDownloads.bind(this);
this.onGetDownloadsResponse = this.onGetDownloadsResponse.bind(this);
this.onGetDownloadsError = this.onGetDownloadsError.bind(this);
this.state = {
downloads: null
, isLoadingDownloads: false
, wasErrorLoadingDownloads: false
, downloadsLoadingEllipsis: "..."
};
}
onGetDownloads () {
this.setState({
isLoadingDownloads: true
});
const timeout = setInterval(() => {
this.setState(state => ({
downloadsLoadingEllipsis: getNextEllipsis(
state.downloadsLoadingEllipsis)
}));
}, 500);
fetch(ENDPOINT_URL)
.then(res => {
window.clearTimeout(timeout);
return res.json()
})
.then(this.onGetDownloadsResponse)
.catch(this.onGetDownloadsError);
}
async onGetDownloadsResponse (res) {
const platformInfo = await browser.runtime.getPlatformInfo();
const downloads = res.assets
.reduce((acc, asset) => {
const download = {
name: asset.name
, url: asset.browser_download_url
};
<div className={infoClasses}>
<div className="bridge__status">
<img className="bridge__status-icon"
width="60" height="60"
src={ statusIcon } />
const platformExtensions = {
"exe": "win"
, "pkg": "mac"
, "deb": "deb"
, "rpm": "rpm"
};
<h2 className="bridge__status-text">
{ statusText }
</h2>
</div>
const fileExtension = asset.name.match(/.*\.(.*)$/).pop();
{ do {
if (props.info) {
<BridgeStats info={ props.info }/>
} else {
// TODO: Download links
}
}}
</div>
}
}}
</div>
);
if (fileExtension in platformExtensions) {
const platform = platformExtensions[fileExtension];
acc[platform] = download;
}
return acc;
}, { platform: platformInfo.os });
this.setState({
isLoadingDownloads: false
, downloads
});
}
onGetDownloadsError (err) {
this.setState({
isLoadingDownloads: false
, wasErrorLoadingDownloads: true
});
}
render () {
return (
<div className="bridge">
{ do {
if (this.props.loading) {
<div className="bridge__loading">
{ _("optionsBridgeLoading") }
<progress></progress>
</div>
} else {
const infoClasses = `bridge__info ${this.props.info
? "bridge__info--found"
: "bridge__info--not-found"}`;
const [ statusIcon, statusTitle, statusText ] = do {
if (!this.props.info) {
[ "assets/icons8-cancel-120.png"
, _("optionsBridgeNotFoundStatusTitle")
, _("optionsBridgeNotFoundStatusText") ]
} else {
if (this.props.info.isVersionCompatible) {
[ "assets/icons8-ok-120.png"
, _("optionsBridgeFoundStatusTitle") ]
} else {
[ "assets/icons8-warn-120.png"
, _("optionsBridgeIssueStatusTitle") ]
}
}
};
<div className={infoClasses}>
<div className="bridge__status">
<img className="bridge__status-icon"
width="60" height="60"
src={ statusIcon } />
<h2 className="bridge__status-title">
{ statusTitle }
</h2>
{ do {
if (statusText) {
<p className="bridge__status-text">
{ statusText }
</p>
}
}}
</div>
{ do {
if (this.props.info) {
<BridgeStats info={ this.props.info }/>
}
}}
</div>
}
}}
{ do {
if (!this.props.loading
&& (!this.props.info
|| !this.props.info.isVersionCompatible)) {
<div className="bridge__download-info">
<h2 className="bridge__download-info-title">
{ _("optionsBridgeDownloadsTitle") }
</h2>
{ do {
if (this.state.downloads) {
<BridgeDownloads info={ this.state.downloads }/>
} else if (this.state.wasErrorLoadingDownloads) {
<div className="bridge__download-info-get-error">
{ _("optionsBridgeDownloadsGetFailed") }
</div>
} else {
<button className="bridge__download-info-get"
onClick={ this.onGetDownloads }
disabled={ this.state.isLoadingDownloads }>
{ do {
if (this.state.isLoadingDownloads) {
_("optionsBridgeDownloadsLoading"
, getNextEllipsis(this.state.downloadsLoadingEllipsis));
} else {
_("optionsBridgeDownloadsGet");
}
}}
</button>
}
}}
</div>
}
}}
</div>
);
}
}

View File

@@ -1,5 +1,6 @@
:root {
--border-color: rgb(225, 225, 225);;
--border-color: rgb(225, 225, 225);
--secondary-color: rgb(125, 125, 125);
}
*:invalid {
@@ -23,7 +24,7 @@
}
#status-line {
color: graytext;
color: var(--secondary-color);
}
@@ -73,13 +74,21 @@
padding-inline-end: 25px;
}
.bridge__status-text {
.bridge__status-title {
margin: initial;
font-weight: 500;
font-size: 1.5em;
white-space: nowrap;
}
.bridge__status-text {
margin: initial;
margin-top: 5px;
font-size: 1.15em;
font-weight: 300;
text-align: center;
}
.bridge__info--not-found .bridge__status {
flex-direction: row;
}
@@ -89,7 +98,7 @@
.bridge__info--not-found .bridge__status-icon {
margin-inline-end: 10px;
}
.bridge__info--not-found .bridge__status-text {
.bridge__info--not-found .bridge__status-title {
font-weight: normal;
white-space: normal;
}
@@ -108,6 +117,37 @@
vertical-align: top;
}
.bridge__download-info {
margin-top: 30px;
display: flex;
flex-direction: column;
width: -moz-available;
}
.bridge__download-info-get {
align-self: flex-start;
justify-content: center;
}
.bridge__download-info-title {
font-weight: 500;
font-size: 1.25em;
}
.bridge-downloads {
display: flex;
}
.bridge-downloads__download {
}
.bridge-downloads__linux {
display: flex;
flex-direction: column;
}
.category {
border: initial;
@@ -120,7 +160,7 @@
}
.category:disabled {
color: graytext;
color: var(--secondary-color);
}
#form > .category:not(:first-child) {
@@ -148,7 +188,7 @@
}
.category__description {
color: graytext;
color: var(--secondary-color);
margin-top: initial;
max-width: 60ch;
}

View File

@@ -3,6 +3,8 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { getNextEllipsis } from "../lib/utils";
const _ = browser.i18n.getMessage;
// macOS styles
@@ -168,14 +170,9 @@ class Receiver extends Component {
});
setInterval(() => {
this.setState({
ellipsis: do {
if (this.state.ellipsis === "") ".";
else if (this.state.ellipsis === ".") "..";
else if (this.state.ellipsis === "..") "...";
else if (this.state.ellipsis === "...") "";
}
});
this.setState(state => ({
ellipsis: getNextEllipsis(state.ellipsis)
}));
}, 500);
}

View File

@@ -1,22 +1,22 @@
body {
background: rgb(236, 236, 236);
font: menu;
}
button,
select {
font: inherit;
}
button:not([disabled]):hover:active {
color: -moz-mac-buttonactivetext;
}
.receiver-address,
.receiver-status {
font: message-box;
}
.receiver-connect {
height: 22px;
}
body {
background: rgb(236, 236, 236);
font: menu;
}
button,
select {
font: inherit;
}
button:not([disabled]):hover:active {
color: -moz-mac-buttonactivetext;
}
.receiver-address,
.receiver-status {
font: message-box;
}
.receiver-connect {
height: 22px;
}