mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-08 08:39:59 +00:00
Add bridge downloads to options page
This commit is contained in:
@@ -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
10
ext/src/lib/utils.js
Normal file
@@ -0,0 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
export function getNextEllipsis (ellipsis) {
|
||||
return do {
|
||||
if (ellipsis === "") ".";
|
||||
else if (ellipsis === ".") "..";
|
||||
else if (ellipsis === "..") "...";
|
||||
else if (ellipsis === "...") "";
|
||||
};
|
||||
}
|
||||
@@ -31,7 +31,9 @@
|
||||
"page": "options/index.html"
|
||||
}
|
||||
, "permissions": [
|
||||
"menus"
|
||||
"downloads"
|
||||
, "downloads.open"
|
||||
, "menus"
|
||||
, "nativeMessaging"
|
||||
, "storage"
|
||||
, "webNavigation"
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user