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. 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). 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": { "compilerOptions": {
"allowJs": true "esModuleInterop": true
, "esModuleInterop": true
, "module": "commonjs" , "module": "commonjs"
, "noImplicitAny": true , "noImplicitAny": true
, "removeComments": true , "removeComments": true

View File

@@ -193,6 +193,10 @@
"message": "Sync receiver state with media element" "message": "Sync receiver state with media element"
, "description": "Media casting sync checkbox label." , "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": { , "optionsMediaStopOnUnload": {
"message": "Stop receiver playback on page unload" "message": "Stop receiver playback on page unload"
, "description": "Media stop on unload checkbox label." , "description": "Media stop on unload checkbox label."
@@ -215,6 +219,39 @@
, "description": "HTTP server port input label." , "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": { , "optionsUserAgentWhitelistCategoryName": {
"message": "User agent whitelist" "message": "User agent whitelist"
, "description": "Options page whitelist category title." , "description": "Options page whitelist category title."
@@ -282,6 +319,10 @@
"message": "Receiver app ID:" "message": "Receiver app ID:"
, "description": "Mirroring app ID input label." , "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": { , "optionsReset": {
"message": "Restore Defaults" "message": "Restore Defaults"

View File

@@ -1,5 +1,7 @@
"use strict"; "use strict";
import { ReceiverSelectorType } from "./receiver_selectors";
export interface Options { export interface Options {
bridgeApplicationName: string; bridgeApplicationName: string;
mediaEnabled: boolean; mediaEnabled: boolean;
@@ -9,6 +11,12 @@ export interface Options {
localMediaServerPort: number; localMediaServerPort: number;
mirroringEnabled: boolean; mirroringEnabled: boolean;
mirroringAppId: string; mirroringAppId: string;
receiverSelectorType: ReceiverSelectorType;
// TODO: Implement
receiverSelectorCloseIfFocusLost: boolean;
receiverSelectorWaitForConnection: boolean;
userAgentWhitelistEnabled: boolean; userAgentWhitelistEnabled: boolean;
userAgentWhitelist: string[]; userAgentWhitelist: string[];
@@ -24,6 +32,9 @@ const options: Options = {
, localMediaServerPort: 9555 , localMediaServerPort: 9555
, mirroringEnabled: false , mirroringEnabled: false
, mirroringAppId: MIRRORING_APP_ID , mirroringAppId: MIRRORING_APP_ID
, receiverSelectorType: ReceiverSelectorType.Popup
, receiverSelectorCloseIfFocusLost: true
, receiverSelectorWaitForConnection: false
, userAgentWhitelistEnabled: true , userAgentWhitelistEnabled: true
, userAgentWhitelist: [ , userAgentWhitelist: [
"https://www.netflix.com/*" "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(
await options.get("receiverSelectorType"));
const receiverSelector = getReceiverSelector(os === "mac"
? ReceiverSelectorType.NativeMac
: ReceiverSelectorType.Popup);
function onReceiverSelectorSelected ( function onReceiverSelectorSelected (

View File

@@ -47,27 +47,13 @@ export default class EditableList extends Component<
public render () { public render () {
return ( return (
<div className="editable-list"> <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 { this.state.rawView
? ( ? (
<textarea className="editable-list__raw-view" <textarea className="editable-list__raw-view"
rows={ this.props.data.length} rows={ this.props.data.length > 10
value={ this.state.rawViewValue} ? this.props.data.length
: 10 }
value={ this.state.rawViewValue }
onChange={ this.handleRawViewTextAreaChange } onChange={ this.handleRawViewTextAreaChange }
ref={ el => { this.rawViewTextArea = el; }}> ref={ el => { this.rawViewTextArea = el; }}>
</textarea> </textarea>
@@ -88,15 +74,30 @@ export default class EditableList extends Component<
onEdit={ this.handleNewItemEdit } onEdit={ this.handleNewItemEdit }
editing={ true } /> } 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> </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> </div>
); );
} }

View File

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

View File

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

View File

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