diff --git a/ext/src/_locales/en/messages.json b/ext/src/_locales/en/messages.json
index 3a97187..a776b10 100755
--- a/ext/src/_locales/en/messages.json
+++ b/ext/src/_locales/en/messages.json
@@ -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"
diff --git a/ext/src/lib/utils.js b/ext/src/lib/utils.js
new file mode 100644
index 0000000..c5315ef
--- /dev/null
+++ b/ext/src/lib/utils.js
@@ -0,0 +1,10 @@
+"use strict";
+
+export function getNextEllipsis (ellipsis) {
+ return do {
+ if (ellipsis === "") ".";
+ else if (ellipsis === ".") "..";
+ else if (ellipsis === "..") "...";
+ else if (ellipsis === "...") "";
+ };
+}
diff --git a/ext/src/manifest.json b/ext/src/manifest.json
index 2d3ff29..c646bd1 100755
--- a/ext/src/manifest.json
+++ b/ext/src/manifest.json
@@ -31,7 +31,9 @@
"page": "options/index.html"
}
, "permissions": [
- "menus"
+ "downloads"
+ , "downloads.open"
+ , "menus"
, "nativeMessaging"
, "storage"
, "webNavigation"
diff --git a/ext/src/options/Bridge.jsx b/ext/src/options/Bridge.jsx
index 67fe330..47d88ad 100644
--- a/ext/src/options/Bridge.jsx
+++ b/ext/src/options/Bridge.jsx
@@ -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) => (
+
+
+
+
+
+
+
+
+
+);
+
const BridgeStats = (props) => (
@@ -49,54 +87,176 @@ const BridgeStats = (props) => (
);
-export default (props) => (
-
- { do {
- if (props.loading) {
-
- { _("optionsBridgeLoading") }
-
-
- } 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
};
-
-
-

+ const platformExtensions = {
+ "exe": "win"
+ , "pkg": "mac"
+ , "deb": "deb"
+ , "rpm": "rpm"
+ };
-
- { statusText }
-
-
+ const fileExtension = asset.name.match(/.*\.(.*)$/).pop();
- { do {
- if (props.info) {
-
- } else {
- // TODO: Download links
- }
- }}
-
- }
- }}
-
-);
+ 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 (
+
+ { do {
+ if (this.props.loading) {
+
+ { _("optionsBridgeLoading") }
+
+
+ } 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") ]
+ }
+ }
+ };
+
+
+
+

+
+
+ { statusTitle }
+
+
+ { do {
+ if (statusText) {
+
+ { statusText }
+
+ }
+ }}
+
+
+ { do {
+ if (this.props.info) {
+
+ }
+ }}
+
+ }
+ }}
+
+ { do {
+ if (!this.props.loading
+ && (!this.props.info
+ || !this.props.info.isVersionCompatible)) {
+
+
+ { _("optionsBridgeDownloadsTitle") }
+
+ { do {
+ if (this.state.downloads) {
+
+ } else if (this.state.wasErrorLoadingDownloads) {
+
+ { _("optionsBridgeDownloadsGetFailed") }
+
+ } else {
+
+ }
+ }}
+
+ }
+ }}
+
+ );
+ }
+}
diff --git a/ext/src/options/styles/index.css b/ext/src/options/styles/index.css
index 7d55a20..846de40 100644
--- a/ext/src/options/styles/index.css
+++ b/ext/src/options/styles/index.css
@@ -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;
}
diff --git a/ext/src/popup/index.jsx b/ext/src/popup/index.jsx
index ac47d28..d9e4064 100755
--- a/ext/src/popup/index.jsx
+++ b/ext/src/popup/index.jsx
@@ -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);
}
diff --git a/ext/src/popup/styles/mac.css b/ext/src/popup/styles/mac.css
index 4c753a2..1616f28 100755
--- a/ext/src/popup/styles/mac.css
+++ b/ext/src/popup/styles/mac.css
@@ -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;
+}