mirror of
https://github.com/responsively-org/responsively-app
synced 2024-11-14 00:17:12 +00:00
Merge conflicts resolved
This commit is contained in:
commit
92a6f35ca1
27 changed files with 728 additions and 29144 deletions
|
@ -307,6 +307,15 @@
|
|||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mrfelfel",
|
||||
"name": "mrfelfel",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/19575588?v=4",
|
||||
"profile": "https://github.com/mrfelfel",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 5,
|
||||
|
|
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: responsively
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
@ -138,6 +138,7 @@ Thanks go to these wonderful people ([emoji key](https://allcontributors.org/doc
|
|||
<td align="center"><a href="https://github.com/JayArya"><img src="https://avatars0.githubusercontent.com/u/42388314?v=4" width="100px;" alt=""/><br /><sub><b>Jayant Arya</b></sub></a><br /><a href="https://github.com/responsively-org/responsively-app/commits?author=JayArya" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/JohnRawlins"><img src="https://avatars3.githubusercontent.com/u/42707277?v=4" width="100px;" alt=""/><br /><sub><b>John Rawlins</b></sub></a><br /><a href="https://github.com/responsively-org/responsively-app/commits?author=JohnRawlins" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/lepasq"><img src="https://avatars3.githubusercontent.com/u/53230128?v=4" width="100px;" alt=""/><br /><sub><b>lepasq</b></sub></a><br /><a href="https://github.com/responsively-org/responsively-app/commits?author=lepasq" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/mrfelfel"><img src="https://avatars0.githubusercontent.com/u/19575588?v=4" width="100px;" alt=""/><br /><sub><b>mrfelfel</b></sub></a><br /><a href="https://github.com/responsively-org/responsively-app/commits?author=mrfelfel" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import cx from 'classnames';
|
||||
import FavIconOff from '@material-ui/icons/StarBorder';
|
||||
|
@ -20,6 +19,7 @@ import UrlSearchResults from '../UrlSearchResults';
|
|||
import commonStyles from '../common.styles.css';
|
||||
import styles from './style.css';
|
||||
import debounce from 'lodash/debounce';
|
||||
import {notifyPermissionToHandleReloadOrNewAddress} from '../../utils/permissionUtils.js';
|
||||
|
||||
type Props = {
|
||||
address: string,
|
||||
|
@ -38,9 +38,9 @@ class AddressBar extends React.Component<Props> {
|
|||
this.state = {
|
||||
userTypedAddress: props.address,
|
||||
previousAddress: props.address,
|
||||
finalUrlResult: null,
|
||||
suggestionList: [],
|
||||
canShowSuggestions: false,
|
||||
cursor: 0,
|
||||
cursor: null,
|
||||
};
|
||||
this.inputRef = React.createRef();
|
||||
}
|
||||
|
@ -64,14 +64,18 @@ class AddressBar extends React.Component<Props> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
suggestionList,
|
||||
canShowSuggestions,
|
||||
cursor,
|
||||
userTypedAddress,
|
||||
} = this.state;
|
||||
const showSuggestions =
|
||||
canShowSuggestions && !this._isSuggestionListEmpty();
|
||||
return (
|
||||
<div
|
||||
className={`${styles.addressBarContainer} ${
|
||||
this.state.finalUrlResult
|
||||
? this.state.finalUrlResult.length && this.state.canShowSuggestions
|
||||
? styles.active
|
||||
: ''
|
||||
: ''
|
||||
showSuggestions ? styles.active : ''
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
|
@ -81,7 +85,7 @@ class AddressBar extends React.Component<Props> {
|
|||
name="address"
|
||||
className={styles.addressInput}
|
||||
placeholder="https://your-website.com"
|
||||
value={this.state.userTypedAddress}
|
||||
value={userTypedAddress}
|
||||
onKeyDown={this._handleKeyDown}
|
||||
onChange={this._handleInputChange}
|
||||
/>
|
||||
|
@ -100,9 +104,7 @@ class AddressBar extends React.Component<Props> {
|
|||
>
|
||||
<div
|
||||
className={cx(commonStyles.flexAlignVerticalMiddle)}
|
||||
onClick={() =>
|
||||
this.props.toggleBookmark(this.state.userTypedAddress)
|
||||
}
|
||||
onClick={() => this.props.toggleBookmark(userTypedAddress)}
|
||||
>
|
||||
<Icon
|
||||
type={this.props.isBookmarked ? 'starFull' : 'star'}
|
||||
|
@ -172,70 +174,110 @@ class AddressBar extends React.Component<Props> {
|
|||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.finalUrlResult?.length && this.state.canShowSuggestions ? (
|
||||
{showSuggestions ? (
|
||||
<UrlSearchResults
|
||||
filteredSearchResults={this.state.finalUrlResult}
|
||||
cursorIndex={this.state.cursor}
|
||||
filteredSearchResults={suggestionList}
|
||||
cursorIndex={cursor}
|
||||
handleUrlChange={this._onSearchedUrlClick}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_handleInputChange = e => {
|
||||
this.setState(
|
||||
{userTypedAddress: e.target.value, canShowSuggestions: true, cursor: 0},
|
||||
() => {
|
||||
const {value} = e.target;
|
||||
if (value) {
|
||||
this.setState({userTypedAddress: value, canShowSuggestions: true}, () => {
|
||||
this._filterExistingUrl();
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
this.setState({userTypedAddress: value, suggestionList: []}, () => {
|
||||
this._hideSuggestions();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_handleKeyDown = e => {
|
||||
const {cursor, suggestionList} = this.state;
|
||||
if (e.key === 'Enter') {
|
||||
this.inputRef.current.blur();
|
||||
this.setState(
|
||||
{
|
||||
finalUrlResult: [],
|
||||
suggestionList: [],
|
||||
canShowSuggestions: false,
|
||||
cursor: null,
|
||||
},
|
||||
() => {
|
||||
this._onChange();
|
||||
}
|
||||
);
|
||||
} else if (e.key === 'ArrowUp' && this.state.cursor > 0) {
|
||||
this.setState(prevState => ({
|
||||
cursor: prevState.cursor - 1,
|
||||
userTypedAddress: this.state.finalUrlResult[prevState.cursor - 1].url,
|
||||
canShowSuggestions: true,
|
||||
}));
|
||||
} else if (
|
||||
e.key === 'ArrowDown' &&
|
||||
this.state.cursor < this.state.finalUrlResult.length - 1
|
||||
) {
|
||||
this.setState(prevState => ({
|
||||
cursor: prevState.cursor + 1,
|
||||
userTypedAddress: this.state.finalUrlResult[prevState.cursor + 1].url,
|
||||
canShowSuggestions: true,
|
||||
}));
|
||||
} else if (e.key === 'ArrowUp' && !this._isSuggestionListEmpty()) {
|
||||
// if the suggestion list just opened or the first suggestion is selected set the cursor to the last suggestion
|
||||
if (cursor === null || cursor === 0) {
|
||||
this._openSuggestionListAndSetCursorAt(suggestionList.length - 1);
|
||||
// if cursor is down move it up by subtracting 1
|
||||
} else if (cursor > 0) {
|
||||
this._handleSuggestionSelection(-1);
|
||||
}
|
||||
} else if (e.key === 'ArrowDown' && !this._isSuggestionListEmpty()) {
|
||||
// if the suggestion list just opened or the last suggestion is selected set the cursor to the first suggestion
|
||||
if (cursor === null || cursor === suggestionList.length - 1) {
|
||||
this._openSuggestionListAndSetCursorAt(0);
|
||||
// if cursor is up move it down by adding 1
|
||||
} else if (cursor < suggestionList.length - 1) {
|
||||
this._handleSuggestionSelection(1);
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
this.setState(prevState => ({
|
||||
canShowSuggestions: false,
|
||||
}));
|
||||
this._hideSuggestions();
|
||||
}
|
||||
};
|
||||
|
||||
_hideSuggestions = () => {
|
||||
this.setState({
|
||||
canShowSuggestions: false,
|
||||
cursor: null,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Open suggestion list and set current selection at cursor position.
|
||||
* @param {number} cursor Cursor position.
|
||||
*/
|
||||
_openSuggestionListAndSetCursorAt = cursor => {
|
||||
this.setState(prevState => ({
|
||||
cursor,
|
||||
userTypedAddress: this.state.suggestionList[cursor].url,
|
||||
canShowSuggestions: true,
|
||||
}));
|
||||
};
|
||||
/**
|
||||
* Handles the suggestion selection on arrow key up and down.
|
||||
* @param {number} direction Indicates the direction. 1 for down, -1 for up.
|
||||
*/
|
||||
_handleSuggestionSelection = direction => {
|
||||
const modifier = 1 * direction;
|
||||
this.setState(prevState => ({
|
||||
cursor: prevState.cursor + modifier,
|
||||
userTypedAddress: this.state.suggestionList[prevState.cursor + modifier]
|
||||
.url,
|
||||
canShowSuggestions: true,
|
||||
}));
|
||||
};
|
||||
|
||||
_isSuggestionListEmpty = () => this.state.suggestionList.length === 0;
|
||||
|
||||
_handleClickOutside = () => {
|
||||
this._hideSuggestions();
|
||||
};
|
||||
|
||||
_onChange = () => {
|
||||
if (!this.state.userTypedAddress) {
|
||||
if (!this.state.userTypedAddress || !this.props.onChange) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
this.props.onChange &&
|
||||
this.props.onChange(this._normalize(this.state.userTypedAddress), true)
|
||||
);
|
||||
|
||||
notifyPermissionToHandleReloadOrNewAddress();
|
||||
this.props.onChange(this._normalize(this.state.userTypedAddress), true);
|
||||
};
|
||||
|
||||
_onSearchedUrlClick = (url, index) => {
|
||||
|
@ -245,7 +287,7 @@ class AddressBar extends React.Component<Props> {
|
|||
|
||||
this.setState({
|
||||
userTypedAddress: url,
|
||||
finalUrlResult: [],
|
||||
suggestionList: [],
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -264,14 +306,10 @@ class AddressBar extends React.Component<Props> {
|
|||
|
||||
_filterExistingUrl = debounce(() => {
|
||||
const finalResult = searchUrlUtils(this.state.userTypedAddress);
|
||||
this.setState({finalUrlResult: finalResult});
|
||||
this.setState({suggestionList: finalResult.slice(0, MAX_SUGGESTIONS)});
|
||||
}, 300);
|
||||
|
||||
_handleClickOutside = () => {
|
||||
this.setState({
|
||||
finalUrlResult: [],
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const MAX_SUGGESTIONS = 8;
|
||||
|
||||
export default AddressBar;
|
||||
|
|
|
@ -43,9 +43,13 @@ import {lightIconsColor, themeColor} from '../../../constants/colors';
|
|||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
fab: {
|
||||
position: 'absolute',
|
||||
position: 'absolute !important',
|
||||
top: theme.spacing(10),
|
||||
right: theme.spacing(3),
|
||||
color: '#fff !important',
|
||||
backgroundColor: `${themeColor} !important`,
|
||||
borderRadius: '24px !important',
|
||||
padding: '0 16px !important',
|
||||
},
|
||||
extendedIcon: {
|
||||
marginRight: theme.spacing(1),
|
||||
|
|
|
@ -59,6 +59,7 @@ export default function DeviceList({
|
|||
endAdornment: (
|
||||
<InputAdornment>
|
||||
<IconButton
|
||||
className={styles.searchActiveIcon}
|
||||
onClick={() => {
|
||||
setSearchOpen(false);
|
||||
setSearchText('');
|
||||
|
|
|
@ -17,4 +17,9 @@
|
|||
|
||||
.searchIcon {
|
||||
margin: 0 14px 0 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.searchActiveIcon {
|
||||
color: white !important;
|
||||
}
|
||||
|
|
|
@ -45,9 +45,26 @@ export default function DeviceManager(props) {
|
|||
acc[val.id] = val;
|
||||
return acc;
|
||||
}, {});
|
||||
const inactiveDevices = props.browser.allDevices.filter(
|
||||
device => !activeDevicesById[device.id]
|
||||
);
|
||||
|
||||
const currentInactiveDevicesById = devices.inactive.reduce((acc, val) => {
|
||||
acc[val.id] = val;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const devicesById = props.browser.allDevices.reduce((acc, val) => {
|
||||
acc[val.id] = val;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const inactiveDevices = [
|
||||
...props.browser.allDevices.filter(
|
||||
device =>
|
||||
!activeDevicesById[device.id] &&
|
||||
!currentInactiveDevicesById[device.id]
|
||||
),
|
||||
...devices.inactive.filter(device => devicesById[device.id]),
|
||||
];
|
||||
|
||||
setDevices({active: activeDevices, inactive: inactiveDevices});
|
||||
}, [props.browser.devices, props.browser.allDevices]);
|
||||
|
||||
|
@ -75,9 +92,21 @@ export default function DeviceManager(props) {
|
|||
source.droppableId === 'inactive'
|
||||
? devices.inactiveFiltered[source.index]
|
||||
: sourceList[source.index];
|
||||
|
||||
let idx = destination.index;
|
||||
|
||||
if (destination.droppableId === 'inactive') {
|
||||
idx =
|
||||
destination.index < devices.inactiveFiltered.length
|
||||
? devices.inactive.findIndex(
|
||||
d => d.id === devices.inactiveFiltered[destination.index].id
|
||||
)
|
||||
: devices.inactive.length;
|
||||
}
|
||||
|
||||
sourceList.splice(sourceList.indexOf(itemDragged), 1);
|
||||
|
||||
destinationList.splice(destination.index, 0, itemDragged);
|
||||
destinationList.splice(idx, 0, itemDragged);
|
||||
|
||||
updateDevices(devices);
|
||||
};
|
||||
|
|
|
@ -6,6 +6,7 @@ import {ToastContainer} from 'react-toastify';
|
|||
import AddressBar from '../../containers/AddressBar';
|
||||
import ScrollControlsContainer from '../../containers/ScrollControlsContainer';
|
||||
import HttpAuthDialog from '../HttpAuthDialog';
|
||||
import PermissionPopup from '../PermissionPopup';
|
||||
|
||||
import styles from './style.module.css';
|
||||
import NavigationControlsContainer from '../../containers/NavigationControlsContainer';
|
||||
|
@ -20,6 +21,7 @@ const Header = () => (
|
|||
</Grid>
|
||||
<Grid item style={{flex: 1}}>
|
||||
<AddressBar />
|
||||
<PermissionPopup />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<ScrollControlsContainer />
|
||||
|
|
|
@ -12,6 +12,7 @@ import commonStyles from '../common.styles.css';
|
|||
import {iconsColor} from '../../constants/colors';
|
||||
import Cross from '../icons/Cross';
|
||||
import Reload from '../icons/Reload';
|
||||
import {notifyPermissionToHandleReloadOrNewAddress} from '../../utils/permissionUtils.js';
|
||||
|
||||
class NavigationControls extends Component {
|
||||
componentDidMount() {
|
||||
|
@ -58,7 +59,10 @@ class NavigationControls extends Component {
|
|||
<Tooltip title="Reload">
|
||||
<div
|
||||
className={commonStyles.flexAlignVerticalMiddle}
|
||||
onClick={this.props.triggerNavigationReload}
|
||||
onClick={() => {
|
||||
notifyPermissionToHandleReloadOrNewAddress();
|
||||
this.props.triggerNavigationReload();
|
||||
}}
|
||||
>
|
||||
<Reload {...iconProps} padding={4} height={24} width={24} />
|
||||
</div>
|
||||
|
@ -82,7 +86,12 @@ class NavigationControls extends Component {
|
|||
})}
|
||||
/>
|
||||
<Tooltip title="Back">
|
||||
<div onClick={this.props.triggerNavigationBack}>
|
||||
<div
|
||||
onClick={() => {
|
||||
notifyPermissionToHandleReloadOrNewAddress();
|
||||
this.props.triggerNavigationBack();
|
||||
}}
|
||||
>
|
||||
<Icon type="arrowLeft" size="30px" {...iconProps} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
@ -100,7 +109,12 @@ class NavigationControls extends Component {
|
|||
})}
|
||||
/>
|
||||
<Tooltip title="Forward">
|
||||
<div onClick={this.props.triggerNavigationForward}>
|
||||
<div
|
||||
onClick={() => {
|
||||
notifyPermissionToHandleReloadOrNewAddress();
|
||||
this.props.triggerNavigationForward();
|
||||
}}
|
||||
>
|
||||
<Icon type="arrowRight" size="30px" {...iconProps} />
|
||||
{/* <ArrowRightIcon {...iconProps} /> */}
|
||||
</div>
|
||||
|
@ -113,7 +127,11 @@ class NavigationControls extends Component {
|
|||
<Tooltip title="Go to Homepage">
|
||||
<div
|
||||
className={commonStyles.flexAlignVerticalMiddle}
|
||||
onClick={this.props.goToHomepage}
|
||||
onClick={() => {
|
||||
if (this.props.address !== this.props.homepage)
|
||||
notifyPermissionToHandleReloadOrNewAddress();
|
||||
this.props.goToHomepage();
|
||||
}}
|
||||
>
|
||||
<HomeIcon {...iconProps} padding={5} />
|
||||
</div>
|
||||
|
|
145
desktop-app/app/components/PermissionPopup/index.js
Normal file
145
desktop-app/app/components/PermissionPopup/index.js
Normal file
|
@ -0,0 +1,145 @@
|
|||
import React, {useState, useEffect} from 'react';
|
||||
import {ipcRenderer} from 'electron';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import {makeStyles} from '@material-ui/core/styles';
|
||||
import styles from './styles.module.css';
|
||||
import cx from 'classnames';
|
||||
import pubsub from 'pubsub.js';
|
||||
import {
|
||||
HIDE_PERMISSION_POPUP_DUE_TO_RELOAD,
|
||||
PERMISSION_MANAGEMENT_PREFERENCE_CHANGED,
|
||||
} from '../../constants/pubsubEvents';
|
||||
import {
|
||||
getPermissionPageTitle,
|
||||
getPermissionRequestMessage,
|
||||
} from '../../utils/permissionUtils.js';
|
||||
import {PERMISSION_MANAGEMENT_OPTIONS} from '../../constants/permissionsManagement';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
closeButton: {
|
||||
position: 'absolute',
|
||||
right: theme.spacing(1),
|
||||
top: theme.spacing(1),
|
||||
color: theme.palette.grey[500],
|
||||
},
|
||||
}));
|
||||
|
||||
function getMessage(info) {
|
||||
if (info == null) return '';
|
||||
return (
|
||||
<>
|
||||
<strong>{getPermissionPageTitle(info.url)}</strong>
|
||||
{getPermissionRequestMessage(info.permission, info.details)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PermissionPopup() {
|
||||
const classes = useStyles();
|
||||
const [permissionInfos, setPermissionInfos] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const promptHandler = (event, args) => {
|
||||
setPermissionInfos(prev => [...prev, args]);
|
||||
};
|
||||
|
||||
const reloadHandler = () => {
|
||||
setPermissionInfos([]);
|
||||
};
|
||||
|
||||
const subscription = pubsub.subscribe(
|
||||
HIDE_PERMISSION_POPUP_DUE_TO_RELOAD,
|
||||
reloadHandler
|
||||
);
|
||||
ipcRenderer.on('permission-prompt', promptHandler);
|
||||
return () => {
|
||||
ipcRenderer.removeListener('permission-prompt', promptHandler);
|
||||
pubsub.unsubscribe(subscription);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const preferenceChangedHandler = newPreference => {
|
||||
if (newPreference === PERMISSION_MANAGEMENT_OPTIONS.ASK_ALWAYS) return;
|
||||
|
||||
if (newPreference === PERMISSION_MANAGEMENT_OPTIONS.ALLOW_ALWAYS) {
|
||||
permissionInfos.forEach(info => {
|
||||
ipcRenderer.send('permission-response', {...info, allowed: true});
|
||||
});
|
||||
setPermissionInfos([]);
|
||||
} else if (newPreference === PERMISSION_MANAGEMENT_OPTIONS.DENY_ALWAYS) {
|
||||
permissionInfos.forEach(info => {
|
||||
ipcRenderer.send('permission-response', {...info, allowed: false});
|
||||
});
|
||||
setPermissionInfos([]);
|
||||
}
|
||||
};
|
||||
|
||||
const subscription = pubsub.subscribe(
|
||||
PERMISSION_MANAGEMENT_PREFERENCE_CHANGED,
|
||||
preferenceChangedHandler
|
||||
);
|
||||
return () => {
|
||||
pubsub.unsubscribe(subscription);
|
||||
};
|
||||
}, [permissionInfos]);
|
||||
|
||||
function handleClose(allowed) {
|
||||
ipcRenderer.send('permission-response', {...permissionInfos[0], allowed});
|
||||
setPermissionInfos(permissionInfos.slice(1));
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(styles.permissionPopup, {
|
||||
[styles.permissionPopupActive]: permissionInfos.length !== 0,
|
||||
})}
|
||||
>
|
||||
<h4 className={styles.permissionPopupTitle}>
|
||||
Permission Request
|
||||
<Tooltip title="Ignore" placement="left">
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
className={classes.closeButton}
|
||||
onClick={() => handleClose(null)}
|
||||
size="small"
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</h4>
|
||||
<DialogContent className={styles.permissionPopupMsgContainer}>
|
||||
<DialogContentText className={styles.permissionPopupMsg}>
|
||||
{getMessage(permissionInfos[0])}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
type="submit"
|
||||
onClick={() => handleClose(false)}
|
||||
size="small"
|
||||
>
|
||||
Deny
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
onClick={() => handleClose(true)}
|
||||
size="small"
|
||||
>
|
||||
Allow
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
);
|
||||
}
|
30
desktop-app/app/components/PermissionPopup/styles.module.css
Normal file
30
desktop-app/app/components/PermissionPopup/styles.module.css
Normal file
|
@ -0,0 +1,30 @@
|
|||
.permissionPopup {
|
||||
position: absolute;
|
||||
background-color: #252526;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 11px 15px -7px rgba(0, 0, 0, 0.2),
|
||||
0 24px 38px 3px rgba(0, 0, 0, 0.14), 0 9px 46px 8px rgba(0, 0, 0, 0.12);
|
||||
width: 20em;
|
||||
transition: opacity 0.5s ease-out;
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.permissionPopupActive {
|
||||
opacity: 1;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.permissionPopupTitle {
|
||||
margin: 14px;
|
||||
}
|
||||
|
||||
.permissionPopupMsg {
|
||||
margin-bottom: 0 !important;
|
||||
font-size: 0.9rem !important;
|
||||
}
|
||||
|
||||
.permissionPopupMsgContainer {
|
||||
padding: 0 14px !important;
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cx from 'classnames';
|
||||
import styles from './style.css';
|
||||
import DefaultFavIcon from '@material-ui/icons/Public';
|
||||
|
@ -10,53 +11,70 @@ const UrlSearchResults = ({
|
|||
}) => (
|
||||
<div className={cx(styles.searchBarSuggestionsContainer)}>
|
||||
<ul className={cx(styles.searchBarSuggestionsListUl)}>
|
||||
{filteredSearchResults?.map((eachResult, index) => {
|
||||
{filteredSearchResults.map((eachResult, index) => {
|
||||
const favicon = eachResult.pageMeta?.favicons?.[0];
|
||||
const title = eachResult.pageMeta?.title;
|
||||
const url = eachResult.url;
|
||||
return (
|
||||
index < 8 && (
|
||||
<li key={url}>
|
||||
<div
|
||||
className={cx(styles.searchBarSuggestionsListItems, {
|
||||
[styles.searchBarSuggestionsActiveListItems]:
|
||||
cursorIndex === index,
|
||||
})}
|
||||
onClick={() => handleUrlChange(eachResult.url, index)}
|
||||
>
|
||||
<div className={cx(styles.pageFavIconWrapper)}>
|
||||
{favicon ? (
|
||||
<img
|
||||
className={cx(styles.pageFavIcon)}
|
||||
src={favicon}
|
||||
onError={event => {
|
||||
event.target.style.display = 'none';
|
||||
event.target.nextSibling.style.display = 'block';
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className={cx(styles.pageDefaultFavIconWrapper)}>
|
||||
<DefaultFavIcon fontSize="inherit" />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
style={{display: 'none'}}
|
||||
className={cx(styles.pageDefaultFavIconWrapperClassName)}
|
||||
>
|
||||
<li key={url}>
|
||||
<div
|
||||
className={cx(styles.searchBarSuggestionsListItems, {
|
||||
[styles.searchBarSuggestionsActiveListItems]:
|
||||
cursorIndex === index,
|
||||
})}
|
||||
onClick={() => handleUrlChange(eachResult.url, index)}
|
||||
>
|
||||
<div className={cx(styles.pageFavIconWrapper)}>
|
||||
{favicon ? (
|
||||
<img
|
||||
className={cx(styles.pageFavIcon)}
|
||||
src={favicon}
|
||||
onError={event => {
|
||||
event.target.style.display = 'none';
|
||||
event.target.nextSibling.style.display = 'block';
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className={cx(styles.pageDefaultFavIconWrapper)}>
|
||||
<DefaultFavIcon fontSize="inherit" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={cx(styles.pageTitleAndUrlContainer)}>
|
||||
<span className={cx(styles.pageTitle)}>{title}</span>
|
||||
<span className={cx(styles.pageUrl)}>{url}</span>
|
||||
)}
|
||||
<div
|
||||
style={{display: 'none'}}
|
||||
className={cx(styles.pageDefaultFavIconWrapperClassName)}
|
||||
>
|
||||
<DefaultFavIcon fontSize="inherit" />
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
<div className={cx(styles.pageTitleAndUrlContainer)}>
|
||||
<span className={cx(styles.pageTitle)}>{title}</span>
|
||||
<span className={cx(styles.pageUrl)}>{url}</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
UrlSearchResults.propTypes = {
|
||||
filteredSearchResults: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
url: PropTypes.string.isRequired,
|
||||
pageMeta: PropTypes.shape({
|
||||
title: PropTypes.string,
|
||||
favicons: PropTypes.arrayOf(PropTypes.string),
|
||||
}),
|
||||
})
|
||||
),
|
||||
cursorIndex: PropTypes.number,
|
||||
handleUrlChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
UrlSearchResults.defaultProps = {
|
||||
filteredSearchResults: [],
|
||||
cursorIndex: null,
|
||||
};
|
||||
|
||||
export default UrlSearchResults;
|
||||
|
|
|
@ -5,6 +5,7 @@ import Checkbox from '@material-ui/core/Checkbox';
|
|||
import TextField from '@material-ui/core/TextField';
|
||||
import Input from '@material-ui/core/Input';
|
||||
import SettingsIcon from '@material-ui/icons/Settings';
|
||||
import Select from 'react-select';
|
||||
|
||||
import commonStyles from '../common.styles.css';
|
||||
import styles from './styles.module.css';
|
||||
|
@ -12,6 +13,50 @@ import {DEVTOOLS_MODES} from '../../constants/previewerLayouts';
|
|||
import ScreenShotSavePreference from '../ScreenShotSavePreference/index';
|
||||
import {userPreferenceSettings} from '../../settings/userPreferenceSettings';
|
||||
import {SCREENSHOT_MECHANISM} from '../../constants/values';
|
||||
import {notifyPermissionPreferenceChanged} from '../../utils/permissionUtils.js';
|
||||
import {PERMISSION_MANAGEMENT_OPTIONS} from '../../constants/permissionsManagement';
|
||||
|
||||
const selectStyles = {
|
||||
control: selectStyles => ({...selectStyles, backgroundColor: '#ffffff10'}),
|
||||
option: (selectStyles, {data, isDisabled, isFocused, isSelected}) => {
|
||||
const color = 'white';
|
||||
return {
|
||||
...selectStyles,
|
||||
backgroundColor: isDisabled
|
||||
? null
|
||||
: isSelected
|
||||
? '#ffffff40'
|
||||
: isFocused
|
||||
? '#ffffff20'
|
||||
: null,
|
||||
color: 'white',
|
||||
|
||||
':active': {
|
||||
...selectStyles[':active'],
|
||||
backgroundColor: !isDisabled && '#ffffff40',
|
||||
},
|
||||
};
|
||||
},
|
||||
input: selectStyles => ({...selectStyles}),
|
||||
placeholder: selectStyles => ({...selectStyles}),
|
||||
singleValue: (selectStyles, {data}) => ({...selectStyles, color: 'white'}),
|
||||
menu: selectStyles => ({...selectStyles, background: '#4b4b4b', zIndex: 100}),
|
||||
};
|
||||
|
||||
const permissionsOptions = [
|
||||
{
|
||||
value: PERMISSION_MANAGEMENT_OPTIONS.ALLOW_ALWAYS,
|
||||
label: PERMISSION_MANAGEMENT_OPTIONS.ALLOW_ALWAYS,
|
||||
},
|
||||
{
|
||||
value: PERMISSION_MANAGEMENT_OPTIONS.DENY_ALWAYS,
|
||||
label: PERMISSION_MANAGEMENT_OPTIONS.DENY_ALWAYS,
|
||||
},
|
||||
{
|
||||
value: PERMISSION_MANAGEMENT_OPTIONS.ASK_ALWAYS,
|
||||
label: PERMISSION_MANAGEMENT_OPTIONS.ASK_ALWAYS,
|
||||
},
|
||||
];
|
||||
|
||||
export default function UserPreference({
|
||||
devToolsConfig,
|
||||
|
@ -22,6 +67,7 @@ export default function UserPreference({
|
|||
const onChange = (field, value) => {
|
||||
onUserPreferencesChange({...userPreferences, [field]: value});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cx(commonStyles.sidebarContentSection)}>
|
||||
<div className={cx(commonStyles.sidebarContentSectionTitleBar)}>
|
||||
|
@ -164,6 +210,28 @@ export default function UserPreference({
|
|||
onScreenShotSaveLocationChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<div className={cx(commonStyles.sidebarContentSectionContainer)}>
|
||||
<div className={styles.sectionHeader}>Permissions</div>
|
||||
<div className={styles.permissionsSelectorContainer}>
|
||||
<Select
|
||||
options={permissionsOptions}
|
||||
value={
|
||||
permissionsOptions.find(
|
||||
x => x.value === userPreferences?.permissionManagement
|
||||
) || permissionsOptions[0]
|
||||
}
|
||||
onChange={val => {
|
||||
notifyPermissionPreferenceChanged(val.value);
|
||||
onChange('permissionManagement', val.value);
|
||||
}}
|
||||
styles={selectStyles}
|
||||
/>
|
||||
<p className={styles.permissionsSelectorSmallNote}>
|
||||
<strong>Note:</strong> To ensure this behaviour you should restart
|
||||
Responsively
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -31,3 +31,19 @@
|
|||
height: 1px;
|
||||
background-color: #636363;
|
||||
}
|
||||
|
||||
.permissionsSelectorContainer {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.permissionsSelectorSmallNote {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin: 0;
|
||||
font-size: 0.75rem;
|
||||
margin-top: 10px;
|
||||
text-align: left;
|
||||
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 1.66;
|
||||
letter-spacing: 0.03333em;
|
||||
}
|
||||
|
|
9
desktop-app/app/constants/permissionsManagement.js
Normal file
9
desktop-app/app/constants/permissionsManagement.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
const PERMISSION_MANAGEMENT_OPTIONS = {
|
||||
ALLOW_ALWAYS: 'Allow always',
|
||||
DENY_ALWAYS: 'Deny always',
|
||||
ASK_ALWAYS: 'Ask always',
|
||||
};
|
||||
|
||||
Object.freeze(PERMISSION_MANAGEMENT_OPTIONS);
|
||||
|
||||
export {PERMISSION_MANAGEMENT_OPTIONS};
|
|
@ -17,3 +17,8 @@ export const OPEN_CONSOLE_FOR_DEVICE = 'OPEN_CONSOLE_FOR_DEVICE';
|
|||
|
||||
// status bar events
|
||||
export const STATUS_BAR_VISIBILITY_CHANGE = 'status-bar-visibility-change';
|
||||
|
||||
export const HIDE_PERMISSION_POPUP_DUE_TO_RELOAD =
|
||||
'HIDE_PERMISSION_POPUP_DUE_TO_RELOAD';
|
||||
export const PERMISSION_MANAGEMENT_PREFERENCE_CHANGED =
|
||||
'PERMISSION_MANAGEMENT_PREFERENCE_CHANGED';
|
||||
|
|
|
@ -10,8 +10,10 @@ function mapStateToProps(state) {
|
|||
const {
|
||||
navigatorStatus: {backEnabled, forwardEnabled},
|
||||
devices,
|
||||
address,
|
||||
homepage,
|
||||
} = state.browser;
|
||||
return {backEnabled, forwardEnabled, devices};
|
||||
return {backEnabled, forwardEnabled, devices, address, homepage};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
|
|
|
@ -20,6 +20,7 @@ import electron, {
|
|||
webContents,
|
||||
shell,
|
||||
dialog,
|
||||
session,
|
||||
} from 'electron';
|
||||
import settings from 'electron-settings';
|
||||
import log from 'electron-log';
|
||||
|
@ -46,9 +47,11 @@ import {
|
|||
watchFiles,
|
||||
} from './utils/browserSync';
|
||||
import {getHostFromURL} from './utils/urlUtils';
|
||||
import {getPermissionSettingPreference} from './utils/permissionUtils';
|
||||
import browserSync from 'browser-sync';
|
||||
import {captureOnSentry} from './utils/logUtils';
|
||||
import appMetadata from './services/db/appMetadata';
|
||||
import {PERMISSION_MANAGEMENT_OPTIONS} from './constants/permissionsManagement';
|
||||
|
||||
const path = require('path');
|
||||
const chokidar = require('chokidar');
|
||||
|
@ -72,6 +75,7 @@ let devToolsView = null;
|
|||
let fileToOpen = null;
|
||||
|
||||
const httpAuthCallbacks = {};
|
||||
const permissionCallbacks = {};
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
const sourceMapSupport = require('source-map-support');
|
||||
|
@ -319,6 +323,98 @@ const createWindow = async () => {
|
|||
onResize();
|
||||
});
|
||||
|
||||
session.defaultSession.setPermissionRequestHandler(
|
||||
(webContents, permission, callback, details) => {
|
||||
const preferences = getPermissionSettingPreference();
|
||||
|
||||
const reqUrl = webContents.getURL();
|
||||
|
||||
if (permissionCallbacks[reqUrl] == null) permissionCallbacks[reqUrl] = {};
|
||||
|
||||
if (permissionCallbacks[reqUrl][permission] == null) {
|
||||
permissionCallbacks[reqUrl][permission] = {
|
||||
called: false,
|
||||
allowed: null,
|
||||
callbacks: [],
|
||||
};
|
||||
}
|
||||
|
||||
const entry = permissionCallbacks[reqUrl][permission];
|
||||
|
||||
if (preferences === PERMISSION_MANAGEMENT_OPTIONS.ALLOW_ALWAYS) {
|
||||
entry.callbacks.forEach(callback => callback(true));
|
||||
entry.callbacks = [];
|
||||
entry.allowed = true;
|
||||
entry.called = true;
|
||||
return callback(true);
|
||||
}
|
||||
if (preferences === PERMISSION_MANAGEMENT_OPTIONS.DENY_ALWAYS) {
|
||||
entry.callbacks.forEach(callback => callback(false));
|
||||
entry.callbacks = [];
|
||||
entry.allowed = false;
|
||||
entry.called = true;
|
||||
return callback(false);
|
||||
}
|
||||
|
||||
if (entry.called) {
|
||||
if (entry.allowed == null) return;
|
||||
return callback(entry.allowed);
|
||||
}
|
||||
|
||||
if (entry.callbacks.length === 0) {
|
||||
entry.callbacks.push(callback);
|
||||
|
||||
mainWindow.webContents.send('permission-prompt', {
|
||||
url: reqUrl,
|
||||
permission,
|
||||
details,
|
||||
});
|
||||
} else {
|
||||
entry.callbacks.push(callback);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
session.defaultSession.setPermissionCheckHandler(
|
||||
(webContents, permission) => {
|
||||
const reqUrl = webContents.getURL();
|
||||
|
||||
let entry = permissionCallbacks[reqUrl];
|
||||
if (entry != null) entry = entry[permission];
|
||||
|
||||
if (entry == null || !entry.called) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return entry.allowed;
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.on('permission-response', (evnt, ...args) => {
|
||||
if (args[0] == null) return;
|
||||
const {url, permission, allowed} = args[0];
|
||||
|
||||
let entry = permissionCallbacks[url];
|
||||
if (entry != null) entry = entry[permission];
|
||||
|
||||
if (entry != null && !entry.called) {
|
||||
entry.called = true;
|
||||
entry.allowed = allowed;
|
||||
if (allowed != null)
|
||||
entry.callbacks.forEach(callback => callback(allowed));
|
||||
entry.callbacks = [];
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('reset-ignored-permissions', evnt => {
|
||||
Object.entries(permissionCallbacks).forEach(([_, permissions]) => {
|
||||
Object.entries(permissions).forEach(([_, entry]) => {
|
||||
if (entry.called && entry.allowed == null) entry.called = false;
|
||||
entry.callbacks = [];
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on('start-watching-file', async (event, fileInfo) => {
|
||||
let path = fileInfo.path.replace('file://', '');
|
||||
if (process.platform === 'win32') {
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from 'electron';
|
||||
import * as os from 'os';
|
||||
import fs from 'fs';
|
||||
import url from 'url';
|
||||
import {pkg} from './utils/generalUtils';
|
||||
import {
|
||||
getAllShortcuts,
|
||||
|
@ -131,7 +132,12 @@ export default class MenuBuilder {
|
|||
|
||||
win.center();
|
||||
|
||||
win.loadURL(`file://${__dirname}/shortcuts.html`);
|
||||
win.loadURL(
|
||||
url.format({
|
||||
protocol: 'file',
|
||||
pathname: path.join(__dirname, 'shortcuts.html'),
|
||||
})
|
||||
);
|
||||
|
||||
win.once('ready-to-show', () => {
|
||||
win.show();
|
||||
|
@ -189,13 +195,16 @@ export default class MenuBuilder {
|
|||
const selected = dialog.showOpenDialogSync({
|
||||
filters: [{name: 'HTML', extensions: ['htm', 'html']}],
|
||||
});
|
||||
|
||||
if (!selected || !selected.length || !selected[0]) {
|
||||
return;
|
||||
}
|
||||
let filePath = selected[0];
|
||||
if (!filePath.startsWith('file://')) {
|
||||
filePath = `file://${filePath}`;
|
||||
}
|
||||
|
||||
filePath = url.format({
|
||||
protocol: 'file',
|
||||
pathname: filePath,
|
||||
});
|
||||
this.mainWindow.webContents.send('address-change', filePath);
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// @flow
|
||||
import {ipcRenderer, remote} from 'electron';
|
||||
import settings from 'electron-settings';
|
||||
import {isIfStatement} from 'typescript';
|
||||
import trimStart from 'lodash/trimStart';
|
||||
import {
|
||||
NEW_ADDRESS,
|
||||
NEW_ZOOM_LEVEL,
|
||||
|
@ -16,7 +14,6 @@ import {
|
|||
NEW_HOMEPAGE,
|
||||
NEW_USER_PREFERENCES,
|
||||
DELETE_CUSTOM_DEVICE,
|
||||
TOGGLE_BOOKMARK,
|
||||
NEW_DEV_TOOLS_CONFIG,
|
||||
NEW_INSPECTOR_STATUS,
|
||||
NEW_WINDOW_SIZE,
|
||||
|
@ -120,6 +117,7 @@ type UserPreferenceType = {
|
|||
zoomLevel: number,
|
||||
removeFixedPositionedElements: boolean,
|
||||
screenshotMechanism: string,
|
||||
permissionManagement: 'Ask always' | 'Allow always' | 'Deny always',
|
||||
};
|
||||
|
||||
type FilterFieldType = FILTER_FIELDS.OS | FILTER_FIELDS.DEVICE_TYPE;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import settings from 'electron-settings';
|
||||
import {ACTIVE_DEVICES, USER_PREFERENCES} from '../constants/settingKeys';
|
||||
import {SCREENSHOT_MECHANISM} from '../constants/values';
|
||||
import {PERMISSION_MANAGEMENT_OPTIONS} from '../constants/permissionsManagement';
|
||||
|
||||
export function migrateDeviceSchema() {
|
||||
if (settings.get('USER_PREFERENCES')) {
|
||||
|
@ -11,6 +12,7 @@ export function migrateDeviceSchema() {
|
|||
_handleScreenshotFixedElementsPreferences();
|
||||
_handleScreenshotMechanismPreferences();
|
||||
_handleDeviceSchema();
|
||||
_handlePermissionsDefaultPreferences();
|
||||
}
|
||||
|
||||
const _handleDeviceSchema = () => {
|
||||
|
@ -46,4 +48,16 @@ const _handleScreenshotMechanismPreferences = () => {
|
|||
settings.set(USER_PREFERENCES, userPreferences);
|
||||
};
|
||||
|
||||
const _handlePermissionsDefaultPreferences = () => {
|
||||
const userPreferences = settings.get(USER_PREFERENCES) || {};
|
||||
|
||||
if (userPreferences.permissionManagement != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
userPreferences.permissionManagement =
|
||||
PERMISSION_MANAGEMENT_OPTIONS.ALLOW_ALWAYS;
|
||||
settings.set(USER_PREFERENCES, userPreferences);
|
||||
};
|
||||
|
||||
export default {migrateDeviceSchema};
|
||||
|
|
|
@ -96,7 +96,6 @@
|
|||
if (lo === 'escape') return 'Esc';
|
||||
if (lo === 'prtsc') return 'PrtSc';
|
||||
if (lo.startsWith('num')) return getNumPadName(k.slice(3));
|
||||
console.log('k', k);
|
||||
return firstToUpperCase(k);
|
||||
}
|
||||
function mapAccelerator(acc) {
|
||||
|
|
76
desktop-app/app/utils/permissionUtils.js
Normal file
76
desktop-app/app/utils/permissionUtils.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
import pubsub from 'pubsub.js';
|
||||
import {ipcRenderer} from 'electron';
|
||||
import {
|
||||
HIDE_PERMISSION_POPUP_DUE_TO_RELOAD,
|
||||
PERMISSION_MANAGEMENT_PREFERENCE_CHANGED,
|
||||
} from '../constants/pubsubEvents';
|
||||
import {PERMISSION_MANAGEMENT_OPTIONS} from '../constants/permissionsManagement';
|
||||
import {USER_PREFERENCES} from '../constants/settingKeys';
|
||||
import settings from 'electron-settings';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
export function getPermissionPageTitle(url) {
|
||||
if (url == null || url.length === 0) return 'The webpage';
|
||||
try {
|
||||
if (url.startsWith('file://')) {
|
||||
return decodeURIComponent(path.basename(url));
|
||||
}
|
||||
return new URL(url).hostname;
|
||||
} catch {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
export function getDeviceText(device) {
|
||||
if (device === 'audio') return 'microphone';
|
||||
if (device === 'video') return 'camera';
|
||||
return device;
|
||||
}
|
||||
|
||||
// simulating the behavior of google chrome
|
||||
export function getPermissionRequestMessage(permission, details) {
|
||||
if (permission === 'notifications') return 'wants to show notifications';
|
||||
|
||||
if (permission === 'geolocation') return 'wants to know your location';
|
||||
|
||||
if (permission === 'fullscreen') return 'wants to go to full screen';
|
||||
|
||||
if (permission === 'pointerLock')
|
||||
return 'wants to control your pointer movements';
|
||||
|
||||
// see https:github.com/electron/electron/pull/23333
|
||||
if (permission.includes('clipboard') || permission === 'unknown')
|
||||
return 'wants to see text and images copied to the clipboard';
|
||||
|
||||
if (permission === 'media') {
|
||||
let mediaTypes = details?.mediaTypes;
|
||||
if (mediaTypes != null && mediaTypes.length !== 0) {
|
||||
mediaTypes = mediaTypes.map(getDeviceText);
|
||||
if (mediaTypes.length === 1) return `wants to use your ${mediaTypes[0]}`;
|
||||
const last = mediaTypes.pop();
|
||||
return `wants to use your ${mediaTypes.join(', ')} and ${last}`;
|
||||
}
|
||||
return 'wants to use some of your media devices';
|
||||
}
|
||||
|
||||
if (permission.includes('midi')) return 'wants to use your MIDI devices';
|
||||
|
||||
return `is requesting permission for "${permission}"`;
|
||||
}
|
||||
|
||||
export function notifyPermissionToHandleReloadOrNewAddress() {
|
||||
pubsub.publish(HIDE_PERMISSION_POPUP_DUE_TO_RELOAD);
|
||||
ipcRenderer.send('reset-ignored-permissions');
|
||||
}
|
||||
|
||||
export function notifyPermissionPreferenceChanged(newPreference) {
|
||||
pubsub.publish(PERMISSION_MANAGEMENT_PREFERENCE_CHANGED, [newPreference]);
|
||||
}
|
||||
|
||||
export function getPermissionSettingPreference() {
|
||||
return (
|
||||
settings.get(USER_PREFERENCES)?.permissionManagement ||
|
||||
PERMISSION_MANAGEMENT_OPTIONS.ALLOW_ALWAYS
|
||||
);
|
||||
}
|
29033
desktop-app/package-lock.json
generated
29033
desktop-app/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -11,11 +11,15 @@ const populateContributors = () => {
|
|||
}
|
||||
|
||||
function generateFooterRow(json) {
|
||||
if (json == null || !json.length) {
|
||||
throw new Error('Malformed data');
|
||||
}
|
||||
const humanContributors = json.filter((contributor) => contributor.type === "User");
|
||||
let html = '';
|
||||
document.getElementById(
|
||||
'github-contributors__thanks',
|
||||
).innerText = `Thanks to all of our ${json.length} contributors! 🎉👏`;
|
||||
json.forEach((contributor) => {
|
||||
).innerText = `Thanks to all of our ${humanContributors.length} contributors! 🎉👏`;
|
||||
humanContributors.forEach((contributor) => {
|
||||
html += generateProfile(contributor);
|
||||
});
|
||||
document.getElementById('github-contributors__users').innerHTML = html;
|
||||
|
@ -23,7 +27,7 @@ const populateContributors = () => {
|
|||
|
||||
function getContr() {
|
||||
fetch(
|
||||
'https://api.github.com/repos/responsively-org/responsively-app/contributors',
|
||||
'https://api.github.com/repos/responsively-org/responsively-app/contributors?per_page=100',
|
||||
)
|
||||
.then((response) => response.text())
|
||||
.then((text) => JSON.parse(text))
|
||||
|
|
|
@ -29,6 +29,15 @@
|
|||
"dismissText": "Dismiss",
|
||||
"minOpenCount": 0
|
||||
},
|
||||
{
|
||||
"id": "app-growth-udate",
|
||||
"title": "It is been 100 days since the app is launched!",
|
||||
"text": "We have some stats published about it, check it out.",
|
||||
"link": "https://twitter.com/vivek_jonam/status/1298212909118562304",
|
||||
"okText": "Open",
|
||||
"dismissText": "Dismiss",
|
||||
"minOpenCount": 1
|
||||
},
|
||||
{
|
||||
"id": "twitter-handle-intro",
|
||||
"title": "Keep yourselves informed!",
|
||||
|
|
Loading…
Reference in a new issue