Add receiver selector options + misc options page improvements

This commit is contained in:
hensm
2019-07-19 18:45:03 +01:00
parent b6f6bd7139
commit 2fe72ed24c
9 changed files with 309 additions and 99 deletions

View File

@@ -11,6 +11,43 @@ issues if you're going to work on something to avoid duplication of effort.
Submit an issue for new features before submitting a PR.
## Compatibility Report
## Compatibility Reports
Compatibility reports are always helpful. Use the "Compatibility Report" issue template. Ensure you have a working environment and that the site is in the whitelist (check options page).
## Localizations
Missing strings:
* `es`
* `optionsMediaSyncElementDescription`
* `optionsReceiverSelectorCategoryName`
* `optionsReceiverSelectorCategoryDescription`
* `optionsReceiverSelectorType`
* `optionsReceiverSelectorTypeBrowser`
* `optionsReceiverSelectorTypeNative`
* `optionsReceiverSelectorWaitForConnection`
* `optionsReceiverSelectorWaitForConnectionDescription`
* `optionsReceiverSelectorCloseIfFocusLost`
* `optionsMirroringAppIdDescription`
* `nl`
* `popupMediaTypeApp`
* `popupMediaTypeTab`
* `popupMediaTypeScreen`
* `popupMediaTypeFile`
* `popupMediaSelectCastLabel`
* `popupMediaSelectToLabel`
* `contextAddToWhitelist`
* `contextAddToWhitelistRecommended`
* `contextAddToWhitelistAdvancedAdd`
* `optionsMediaSyncElementDescription`
* `optionsReceiverSelectorCategoryName`
* `optionsReceiverSelectorCategoryDescription`
* `optionsReceiverSelectorType`
* `optionsReceiverSelectorTypeBrowser`
* `optionsReceiverSelectorTypeNative`
* `optionsReceiverSelectorWaitForConnection`
* `optionsReceiverSelectorWaitForConnectionDescription`
* `optionsReceiverSelectorCloseIfFocusLost`
* `optionsMirroringAppIdDescription`

View File

@@ -1,7 +1,6 @@
{
"compilerOptions": {
"allowJs": true
, "esModuleInterop": true
"esModuleInterop": true
, "module": "commonjs"
, "noImplicitAny": true
, "removeComments": true

View File

@@ -193,6 +193,10 @@
"message": "Sync receiver state with media element"
, "description": "Media casting sync checkbox label."
}
, "optionsMediaSyncElementDescription": {
"message": "Synchronize state (playback, volume, captions, etc...) between the media element and the receiver device."
, "description": "Media casting sync option description."
}
, "optionsMediaStopOnUnload": {
"message": "Stop receiver playback on page unload"
, "description": "Media stop on unload checkbox label."
@@ -215,6 +219,39 @@
, "description": "HTTP server port input label."
}
, "optionsReceiverSelectorCategoryName": {
"message": "Receiver selector"
, "description": "Options page receiver selector category title."
}
, "optionsReceiverSelectorCategoryDescription": {
"message": "Receiver device selection interface."
, "description": "Options page receiver selector category description."
}
, "optionsReceiverSelectorType": {
"message": "Type:"
, "description": "Receiver selector type option label."
}
, "optionsReceiverSelectorTypeBrowser": {
"message": "Browser"
, "description": "Receiver selector type browser radio option label."
}
, "optionsReceiverSelectorTypeNative": {
"message": "Native"
, "description": "Receiver selector type native radio option label."
}
, "optionsReceiverSelectorWaitForConnection": {
"message": "Wait for connection"
, "description": "Receiver selector wait for connection option checkbox label."
}
, "optionsReceiverSelectorWaitForConnectionDescription": {
"message": "Keep receiver selector open until the session is established or connection fails."
, "description": "Receiver selector wait for connection option description."
}
, "optionsReceiverSelectorCloseIfFocusLost": {
"message": "Close after losing focus"
, "description": "Receiver selector close if focus lost option checkbox label."
}
, "optionsUserAgentWhitelistCategoryName": {
"message": "User agent whitelist"
, "description": "Options page whitelist category title."
@@ -282,6 +319,10 @@
"message": "Receiver app ID:"
, "description": "Mirroring app ID input label."
}
, "optionsMirroringAppIdDescription": {
"message": "App ID for a registered Chromecast receiver application. Advanced use only. Must be compatible with the default app (see GitHub repo)."
, "description": "Mirroring app ID option description."
}
, "optionsReset": {
"message": "Restore Defaults"

View File

@@ -1,5 +1,7 @@
"use strict";
import { ReceiverSelectorType } from "./receiver_selectors";
export interface Options {
bridgeApplicationName: string;
mediaEnabled: boolean;
@@ -9,6 +11,12 @@ export interface Options {
localMediaServerPort: number;
mirroringEnabled: boolean;
mirroringAppId: string;
receiverSelectorType: ReceiverSelectorType;
// TODO: Implement
receiverSelectorCloseIfFocusLost: boolean;
receiverSelectorWaitForConnection: boolean;
userAgentWhitelistEnabled: boolean;
userAgentWhitelist: string[];
@@ -24,6 +32,9 @@ const options: Options = {
, localMediaServerPort: 9555
, mirroringEnabled: false
, mirroringAppId: MIRRORING_APP_ID
, receiverSelectorType: ReceiverSelectorType.Popup
, receiverSelectorCloseIfFocusLost: true
, receiverSelectorWaitForConnection: false
, userAgentWhitelistEnabled: true
, userAgentWhitelist: [
"https://www.netflix.com/*"

View File

@@ -639,11 +639,8 @@ async function onConnectShim (port: browser.runtime.Port) {
}
const { os } = await browser.runtime.getPlatformInfo();
const receiverSelector = getReceiverSelector(os === "mac"
? ReceiverSelectorType.NativeMac
: ReceiverSelectorType.Popup);
const receiverSelector = getReceiverSelector(
await options.get("receiverSelectorType"));
function onReceiverSelectorSelected (

View File

@@ -47,27 +47,13 @@ export default class EditableList extends Component<
public render () {
return (
<div className="editable-list">
<div className="editable-list__view-actions">
{ this.state.rawView &&
<button className="editable-list__save-raw-button"
onClick={ this.handleSaveRaw }
type="button">
{ _("optionsUserAgentWhitelistSaveRaw") }
</button> }
<button className="editable-list__view-button"
onClick={ this.handleSwitchView }
type="button">
{ this.state.rawView
? _("optionsUserAgentWhitelistBasicView")
: _("optionsUserAgentWhitelistRawView") }
</button>
</div>
<hr />
{ this.state.rawView
? (
<textarea className="editable-list__raw-view"
rows={ this.props.data.length}
value={ this.state.rawViewValue}
rows={ this.props.data.length > 10
? this.props.data.length
: 10 }
value={ this.state.rawViewValue }
onChange={ this.handleRawViewTextAreaChange }
ref={ el => { this.rawViewTextArea = el; }}>
</textarea>
@@ -88,15 +74,30 @@ export default class EditableList extends Component<
onEdit={ this.handleNewItemEdit }
editing={ true } /> }
<div className="editable-list__item editable-list__item-actions">
<button className="editable-list__add-button"
onClick={ this.handleAddItem }
type="button">
{ _("optionsUserAgentWhitelistAddItem") }
</button>
</div>
</ul>
)}
<hr />
<div className="editable-list__view-actions">
{ !this.state.rawView &&
<button className="editable-list__add-button"
onClick={ this.handleAddItem }
type="button">
{ _("optionsUserAgentWhitelistAddItem") }
</button> }
{ this.state.rawView &&
<button className="editable-list__save-raw-button"
onClick={ this.handleSaveRaw }
type="button">
{ _("optionsUserAgentWhitelistSaveRaw") }
</button> }
<button className="editable-list__view-button"
onClick={ this.handleSwitchView }
type="button">
{ this.state.rawView
? _("optionsUserAgentWhitelistBasicView")
: _("optionsUserAgentWhitelistRawView") }
</button>
</div>
</div>
);
}

View File

@@ -13,6 +13,8 @@ import getBridgeInfo, { BridgeInfo } from "../../lib/getBridgeInfo";
import options from "../../lib/options";
import { REMOTE_MATCH_PATTERN_REGEX } from "../../lib/utils";
import { ReceiverSelectorType } from "../../receiver_selectors";
const _ = browser.i18n.getMessage;
@@ -73,6 +75,9 @@ class OptionsApp extends Component<{}, OptionsAppState> {
this.handleInputChange = this.handleInputChange.bind(this);
this.handleWhitelistChange = this.handleWhitelistChange.bind(this);
this.handleReceiverSelectorTypeChange
= this.handleReceiverSelectorTypeChange.bind(this);
this.getWhitelistItemPatternError
= this.getWhitelistItemPatternError.bind(this);
}
@@ -117,58 +122,66 @@ class OptionsApp extends Component<{}, OptionsAppState> {
</p>
<label className="option option--inline">
<input name="mediaEnabled"
type="checkbox"
checked={ this.state.options.mediaEnabled }
onChange={ this.handleInputChange } />
<div className="option__control">
<input name="mediaEnabled"
type="checkbox"
checked={ this.state.options.mediaEnabled }
onChange={ this.handleInputChange } />
</div>
<div className="option__label">
{ _("optionsMediaEnabled") }
</div>
</label>
<label className="option option--inline">
<input name="mediaSyncElement"
type="checkbox"
checked={ this.state.options.mediaSyncElement }
onChange={ this.handleInputChange } />
<div className="option__control">
<input name="mediaSyncElement"
type="checkbox"
checked={ this.state.options.mediaSyncElement }
onChange={ this.handleInputChange } />
</div>
<div className="option__label">
{ _("optionsMediaSyncElement") }
</div>
<div className="option__description">
{ _("optionsMediaSyncElementDescription") }
</div>
</label>
<label className="option option--inline">
<input name="mediaStopOnUnload"
type="checkbox"
checked={ this.state.options.mediaStopOnUnload }
onChange={ this.handleInputChange } />
<div className="option__control">
<input name="mediaStopOnUnload"
type="checkbox"
checked={ this.state.options.mediaStopOnUnload }
onChange={ this.handleInputChange } />
</div>
<div className="option__label">
{ _("optionsMediaStopOnUnload") }
</div>
</label>
<fieldset className="category"
disabled={ !this.state.options.mediaEnabled }>
<legend className="category__name">
<h2>{ _("optionsLocalMediaCategoryName") }</h2>
</legend>
<p className="category__description">
{ _("optionsLocalMediaCategoryDescription") }
</p>
<hr />
<label className="option option--inline">
<label className="option option--inline">
<div className="option__control">
<input name="localMediaEnabled"
type="checkbox"
checked={ this.state.options.localMediaEnabled }
onChange={ this.handleInputChange } />
<div className="option__label">
{ _("optionsLocalMediaEnabled") }
</div>
</label>
</div>
<div className="option__label">
{ _("optionsLocalMediaEnabled") }
</div>
<div className="option__description">
{ _("optionsLocalMediaCategoryDescription") }
</div>
</label>
<label className="option">
<div className="option__label">
{ _("optionsLocalMediaServerPort") }
</div>
<label className="option">
<div className="option__label">
{ _("optionsLocalMediaServerPort") }
</div>
<div className="option__control">
<input name="localMediaServerPort"
type="number"
required
@@ -176,8 +189,8 @@ class OptionsApp extends Component<{}, OptionsAppState> {
max="65535"
value={ this.state.options.localMediaServerPort }
onChange={ this.handleInputChange } />
</label>
</fieldset>
</div>
</label>
</fieldset>
<fieldset className="category">
@@ -189,10 +202,12 @@ class OptionsApp extends Component<{}, OptionsAppState> {
</p>
<label className="option option--inline">
<input name="mirroringEnabled"
type="checkbox"
checked={ this.state.options.mirroringEnabled }
onChange={ this.handleInputChange } />
<div className="option__control">
<input name="mirroringEnabled"
type="checkbox"
checked={ this.state.options.mirroringEnabled }
onChange={ this.handleInputChange } />
</div>
<div className="option__label">
{ _("optionsMirroringEnabled") }
</div>
@@ -202,11 +217,71 @@ class OptionsApp extends Component<{}, OptionsAppState> {
<div className="option__label">
{ _("optionsMirroringAppId") }
</div>
<input name="mirroringAppId"
type="text"
required
value={ this.state.options.mirroringAppId }
onChange={ this.handleInputChange } />
<div className="option__control">
<input name="mirroringAppId"
type="text"
required
value={ this.state.options.mirroringAppId }
onChange={ this.handleInputChange } />
<div className="option__description">
{ _("optionsMirroringAppIdDescription") }
</div>
</div>
</label>
</fieldset>
<fieldset className="category">
<legend className="category__name">
<h2>{ _("optionsReceiverSelectorCategoryName") }</h2>
</legend>
<p className="category__description">
{ _("optionsReceiverSelectorCategoryDescription") }
</p>
{ this.state.platform === "mac" &&
<label className="option">
<div className="option__label">
{ _("optionsReceiverSelectorType") }
</div>
<div className="option__control">
<select name="receiverSelectorType"
value={ this.state.options.receiverSelectorType }
onChange={ this.handleReceiverSelectorTypeChange }>
<option value={ ReceiverSelectorType.Popup }>
{ _("optionsReceiverSelectorTypeBrowser") }
</option>
<option value={ ReceiverSelectorType.NativeMac }>
{ _("optionsReceiverSelectorTypeNative") }
</option>
</select>
</div>
</label> }
<label className="option option--inline">
<div className="option__control">
<input name="receiverSelectorWaitForConnection"
type="checkbox"
checked={ this.state.options.receiverSelectorWaitForConnection }
onChange={ this.handleInputChange } />
</div>
<div className="option__label">
{ _("optionsReceiverSelectorWaitForConnection") }
</div>
<div className="option__description">
{ _("optionsReceiverSelectorWaitForConnectionDescription") }
</div>
</label>
<label className="option option--inline">
<div className="option__control">
<input name="receiverSelectorCloseIfFocusLost"
type="checkbox"
checked={ this.state.options.receiverSelectorCloseIfFocusLost }
onChange={ this.handleInputChange } />
</div>
<div className="option__label">
{ _("optionsReceiverSelectorCloseIfFocusLost") }
</div>
</label>
</fieldset>
@@ -219,10 +294,12 @@ class OptionsApp extends Component<{}, OptionsAppState> {
</p>
<label className="option option--inline">
<input name="userAgentWhitelistEnabled"
type="checkbox"
checked={ this.state.options.userAgentWhitelistEnabled }
onChange={ this.handleInputChange } />
<div className="option__control">
<input name="userAgentWhitelistEnabled"
type="checkbox"
checked={ this.state.options.userAgentWhitelistEnabled }
onChange={ this.handleInputChange } />
</div>
<div className="option__label">
{ _("optionsUserAgentWhitelistEnabled") }
</div>
@@ -232,10 +309,12 @@ class OptionsApp extends Component<{}, OptionsAppState> {
<div className="option__label">
{ _("optionsUserAgentWhitelistContent") }
</div>
<EditableList data={ this.state.options.userAgentWhitelist }
onChange={ this.handleWhitelistChange }
itemPattern={ REMOTE_MATCH_PATTERN_REGEX }
itemPatternError={ this.getWhitelistItemPatternError } />
<div className="option__control">
<EditableList data={ this.state.options.userAgentWhitelist }
onChange={ this.handleWhitelistChange }
itemPattern={ REMOTE_MATCH_PATTERN_REGEX }
itemPatternError={ this.getWhitelistItemPatternError } />
</div>
</div>
</fieldset>
@@ -311,10 +390,17 @@ class OptionsApp extends Component<{}, OptionsAppState> {
}
private handleInputChange (ev: React.ChangeEvent<HTMLInputElement>) {
const { target } = ev;
this.setState(currentState => {
currentState.options[ev.target.name] = getInputValue(ev.target);
return currentState;
});
}
private handleReceiverSelectorTypeChange (
ev: React.ChangeEvent<HTMLSelectElement>) {
this.setState(currentState => {
currentState.options[target.name] = getInputValue(target);
currentState.options[ev.target.name] = parseInt(ev.target.value);
return currentState;
});
}

View File

@@ -7,6 +7,10 @@
box-shadow: 0 0 1.5px 1px red;
}
body {
margin: 20px 10px;
}
#form {
display: flex;
flex-direction: column;
@@ -27,7 +31,6 @@
color: var(--secondary-color);
}
.bridge {
border-bottom: 1px solid var(--border-color);
margin-bottom: 10px;
@@ -172,14 +175,12 @@
border-top: 1px solid var(--border-color);
}
.category > .category {
padding: 5px 0;
box-shadow: inset 2px 0 0 0 var(--border-color);
}
.category > .category > .category__name,
.category > .category > .category__description {
margin-inline-start: 16px;
.category > hr {
border: initial;
border-top: 1px solid var(--border-color);
grid-column: span 2;
width: 100%;
}
.category__name {
@@ -210,8 +211,26 @@
}
.option--inline {
display: block;
align-items: center;
display: grid;
grid-column-start: 2;
grid-template-columns: min-content 1fr;
grid-template-rows: min-content min-content;
grid-template-areas:
"input label"
". description";
}
.option--inline > input {
grid-area: input;
width: 16px;
}
.option--inline > .option__label {
grid-area: label;
text-align: initial;
}
.option--inline > .option__description {
grid-area: description;
}
.option__label {
@@ -219,7 +238,16 @@
display: inline-block;
}
.option > input {
.option__description {
color: var(--secondary-color);
font-size: smaller;
grid-column: span 2;
margin: 5px 0;
max-width: 45ch;
}
.option > input,
.option > select {
align-self: center;
justify-self: flex-start;
margin-inline-start: initial;
@@ -239,9 +267,8 @@
justify-content: end;
}
.editable-list__view-button,
.editable-list__save-raw-button {
margin-inline-end: 5px;
}
.editable-list hr {
@@ -266,10 +293,10 @@
padding: 0 5px;
}
.editable-list__item:nth-child(even):not(:last-child) {
.editable-list__item:nth-child(even) {
background-color: -moz-eventreerow;
}
.editable-list__item:nth-child(odd):not(:last-child) {
.editable-list__item:nth-child(odd) {
background-color: -moz-oddtreerow;
}
@@ -282,6 +309,10 @@
flex: 1;
}
.editable-list__title + button {
margin-inline-end: 5px;
}
.editable-list__edit-field {
width: -moz-available;
margin-inline-end: 1em;
@@ -294,6 +325,5 @@
}
.editable-list__add-button {
align-self: end;
margin-top: 5px;
margin-inline-end: auto;
}

View File

@@ -22,3 +22,11 @@ button,
select {
height: 22px;
}
input[type="checkbox"],
input[type="radio"] {
height: 16px;
margin-bottom: 1px;
margin-top: 1px;
width: 16px;
}