mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2025-12-06 02:30:47 -08:00
Add option to retrieve workspace icon from the Internet (#117)
This commit is contained in:
parent
96b72a426f
commit
b0fb8cb1a5
14 changed files with 407 additions and 56 deletions
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
<link rel="stylesheet" href="{{ '/assets/main.css' | relative_url }}">
|
||||
<link rel="canonical" href="{{ page.url | replace:'index.html','' | absolute_url }}">
|
||||
<link rel="shortcut icon" href="{{ '/images/favicon.png' | relative_url }}" type="image/png">
|
||||
<link rel="icon" href="{{ '/images/favicon.png' | relative_url }}" type="image/png">
|
||||
<script src="https://kit.fontawesome.com/dd585e114a.js" crossorigin="anonymous"></script>
|
||||
{% if jekyll.environment == 'production' and site.google_analytics %}
|
||||
{% include google-analytics.html %}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"repository": "https://github.com/quanglam2807/singlebox",
|
||||
"author": "Quang Lam <quang.lam2807@gmail.com>",
|
||||
"dependencies": {
|
||||
"cheerio": "1.0.0-rc.3",
|
||||
"download": "7.1.0",
|
||||
"electron-is-dev": "1.1.0",
|
||||
"electron-settings": "3.2.0",
|
||||
|
|
|
|||
76
public/libs/get-website-icon-url-async.js
Normal file
76
public/libs/get-website-icon-url-async.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
const fetch = require('node-fetch');
|
||||
const cheerio = require('cheerio');
|
||||
const url = require('url');
|
||||
|
||||
const getWebsiteIconUrlAsync = (websiteURL) => fetch(websiteURL)
|
||||
.then((res) => res.text())
|
||||
.then((html) => {
|
||||
const $ = cheerio.load(html);
|
||||
// rel=apple-touch-icon
|
||||
// most preferred because it's not transparent
|
||||
const $appleTouchIcon = $('head > link[rel=apple-touch-icon]');
|
||||
if ($appleTouchIcon.length > 0) {
|
||||
// make sure icon is png
|
||||
if ($appleTouchIcon.attr('type') === 'image/png'
|
||||
|| $appleTouchIcon.attr('href').endsWith('.png')) {
|
||||
return url.resolve(websiteURL, $appleTouchIcon.attr('href'));
|
||||
}
|
||||
}
|
||||
// rel=fluid-icon
|
||||
// https://webmasters.stackexchange.com/questions/23696/whats-the-fluid-icon-meta-tag-for
|
||||
const $fluidIcon = $('head > link[rel=fluid-icon]');
|
||||
if ($fluidIcon.length > 100) {
|
||||
return url.resolve(websiteURL, $fluidIcon.attr('href'));
|
||||
}
|
||||
// manifest.json icon
|
||||
// https://developers.google.com/web/fundamentals/web-app-manifest
|
||||
const $manifest = $('head > link[rel=manifest]');
|
||||
if ($('head > link[rel=manifest]').length > 0) {
|
||||
const manifestUrl = url.resolve(websiteURL, $manifest.attr('href'));
|
||||
return fetch(manifestUrl)
|
||||
.then((res) => res.json())
|
||||
.then((manifestJson) => {
|
||||
// return icon with largest size
|
||||
const { icons } = manifestJson;
|
||||
icons.sort((x, y) => parseInt(x.sizes.split('x'), 10) - parseInt(y.sizes.split('x'), 10));
|
||||
return url.resolve(websiteURL, icons[icons.length - 1].src);
|
||||
});
|
||||
}
|
||||
// rel=icon
|
||||
// less preferred because it's not always in high resolution
|
||||
const $icon = $('head > link[rel=icon]');
|
||||
if ($icon.length > 0) {
|
||||
// make sure icon is png
|
||||
if ($icon.attr('type') === 'image/png'
|
||||
|| $icon.attr('href').endsWith('.png')) {
|
||||
return url.resolve(websiteURL, $icon.attr('href'));
|
||||
}
|
||||
}
|
||||
// rel=shortcut icon
|
||||
// less preferred because it's not always in high resolution
|
||||
const $shortcutIcon = $('head > link[rel=\'shortcut icon\']');
|
||||
if ($shortcutIcon.length > 0) {
|
||||
// make sure icon is png
|
||||
if ($shortcutIcon.attr('type') === 'image/png'
|
||||
|| $shortcutIcon.attr('href').endsWith('.png')) {
|
||||
return url.resolve(websiteURL, $shortcutIcon.attr('href'));
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
.then((icon) => {
|
||||
if (!icon) {
|
||||
// try to get /apple-touch-icon.png
|
||||
// https://apple.stackexchange.com/questions/172204/how-apple-com-set-apple-touch-icon
|
||||
const appleTouchIconUrl = url.resolve(websiteURL, '/apple-touch-icon.png');
|
||||
return fetch(appleTouchIconUrl)
|
||||
.then((res) => {
|
||||
if (res.status === 200 && res.headers.get('Content-Type') === 'image/png') return appleTouchIconUrl;
|
||||
return undefined;
|
||||
})
|
||||
.catch(() => undefined);
|
||||
}
|
||||
return icon;
|
||||
});
|
||||
|
||||
module.exports = getWebsiteIconUrlAsync;
|
||||
|
|
@ -45,6 +45,9 @@ const {
|
|||
getPauseNotificationsInfo,
|
||||
} = require('../libs/notifications');
|
||||
|
||||
const sendToAllWindows = require('../libs/send-to-all-windows');
|
||||
const getWebsiteIconUrlAsync = require('../libs/get-website-icon-url-async');
|
||||
|
||||
const createMenu = require('../libs/create-menu');
|
||||
|
||||
const aboutWindow = require('../windows/about');
|
||||
|
|
@ -379,6 +382,18 @@ const loadListeners = () => {
|
|||
global.updateSilent = Boolean(isSilent);
|
||||
autoUpdater.checkForUpdates();
|
||||
});
|
||||
|
||||
// to be replaced with invoke (electron 7+)
|
||||
// https://electronjs.org/docs/api/ipc-renderer#ipcrendererinvokechannel-args
|
||||
ipcMain.on('request-get-website-icon-url', (e, id, url) => {
|
||||
getWebsiteIconUrlAsync(url)
|
||||
.then((iconUrl) => {
|
||||
sendToAllWindows(id, iconUrl);
|
||||
})
|
||||
.catch(() => {
|
||||
sendToAllWindows(id, null);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = loadListeners;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const create = (id) => {
|
|||
|
||||
win = new BrowserWindow({
|
||||
width: 400,
|
||||
height: 600,
|
||||
height: 650,
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
|
|
|
|||
|
|
@ -3,12 +3,17 @@ import PropTypes from 'prop-types';
|
|||
|
||||
import Button from '@material-ui/core/Button';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
import connectComponent from '../../helpers/connect-component';
|
||||
import isUrl from '../../helpers/is-url';
|
||||
import getMailtoUrl from '../../helpers/get-mailto-url';
|
||||
|
||||
import { updateForm, save } from '../../state/add-workspace/actions';
|
||||
import {
|
||||
getIconFromInternet,
|
||||
save,
|
||||
updateForm,
|
||||
} from '../../state/add-workspace/actions';
|
||||
|
||||
import defaultIcon from '../../images/default-icon.png';
|
||||
|
||||
|
|
@ -39,19 +44,25 @@ const styles = (theme) => ({
|
|||
display: 'flex',
|
||||
},
|
||||
avatarLeft: {
|
||||
padding: theme.spacing.unit,
|
||||
paddingTop: theme.spacing.unit,
|
||||
paddingBottom: theme.spacing.unit,
|
||||
paddingLeft: 0,
|
||||
paddingRight: theme.spacing.unit,
|
||||
},
|
||||
avatarRight: {
|
||||
flex: 1,
|
||||
padding: theme.spacing.unit,
|
||||
paddingTop: theme.spacing.unit,
|
||||
paddingBottom: theme.spacing.unit,
|
||||
paddingLeft: theme.spacing.unit,
|
||||
paddingRight: 0,
|
||||
},
|
||||
avatar: {
|
||||
fontFamily: theme.typography.fontFamily,
|
||||
height: 64,
|
||||
width: 64,
|
||||
background: theme.palette.type === 'dark' ? theme.palette.common.white : theme.palette.common.black,
|
||||
background: theme.palette.type === 'dark' ? theme.palette.common.black : theme.palette.common.white,
|
||||
borderRadius: 4,
|
||||
color: theme.palette.getContrastText(theme.palette.type === 'dark' ? theme.palette.common.white : theme.palette.common.black),
|
||||
color: theme.palette.getContrastText(theme.palette.type === 'dark' ? theme.palette.common.black : theme.palette.common.white),
|
||||
fontSize: '32px',
|
||||
lineHeight: '64px',
|
||||
textAlign: 'center',
|
||||
|
|
@ -70,21 +81,27 @@ const styles = (theme) => ({
|
|||
},
|
||||
});
|
||||
|
||||
const getValidIconPath = (iconPath) => {
|
||||
const getValidIconPath = (iconPath, internetIcon) => {
|
||||
if (iconPath) {
|
||||
if (isUrl(iconPath)) return iconPath;
|
||||
return `file://${iconPath}`;
|
||||
}
|
||||
if (internetIcon) {
|
||||
return internetIcon;
|
||||
}
|
||||
return defaultIcon;
|
||||
};
|
||||
|
||||
const AddWorkspaceCustom = ({
|
||||
classes,
|
||||
downloadingIcon,
|
||||
homeUrl,
|
||||
homeUrlError,
|
||||
internetIcon,
|
||||
isMailApp,
|
||||
name,
|
||||
nameError,
|
||||
onGetIconFromInternet,
|
||||
onSave,
|
||||
onUpdateForm,
|
||||
picturePath,
|
||||
|
|
@ -128,18 +145,20 @@ const AddWorkspaceCustom = ({
|
|||
<div className={classes.avatarFlex}>
|
||||
<div className={classes.avatarLeft}>
|
||||
<div className={classes.avatar}>
|
||||
<img alt="Icon" className={classes.avatarPicture} src={getValidIconPath(picturePath)} />
|
||||
<img alt="Icon" className={classes.avatarPicture} src={getValidIconPath(picturePath, internetIcon)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.avatarRight}>
|
||||
<Button
|
||||
variant="contained"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
const { remote } = window.require('electron');
|
||||
const opts = {
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: 'Images', extensions: ['jpg', 'png'] },
|
||||
{ name: 'PNG (Portable Network Graphics)', extensions: ['png'] },
|
||||
{ name: 'JPEG (Joint Photographic Experts Group)', extensions: ['jpg', 'jpeg'] },
|
||||
],
|
||||
};
|
||||
remote.dialog.showOpenDialog(remote.getCurrentWindow(), opts)
|
||||
|
|
@ -150,15 +169,29 @@ const AddWorkspaceCustom = ({
|
|||
});
|
||||
}}
|
||||
>
|
||||
Change Icon
|
||||
Select Local Image...
|
||||
</Button>
|
||||
<Typography variant="caption">
|
||||
PNG or JPEG.
|
||||
</Typography>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
className={classes.buttonBot}
|
||||
disabled={!homeUrl || homeUrlError || downloadingIcon}
|
||||
onClick={() => onGetIconFromInternet(true)}
|
||||
>
|
||||
{downloadingIcon ? 'Downloading Icon from the Internet...' : 'Download Icon from the Internet'}
|
||||
</Button>
|
||||
<br />
|
||||
<Button
|
||||
variant="contained"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
className={classes.buttonBot}
|
||||
onClick={() => onUpdateForm({ picturePath: null })}
|
||||
onClick={() => onUpdateForm({ picturePath: null, internetIcon: null })}
|
||||
disabled={!(picturePath || internetIcon)}
|
||||
>
|
||||
Remove Icon
|
||||
Reset to Default
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -172,28 +205,34 @@ const AddWorkspaceCustom = ({
|
|||
);
|
||||
|
||||
AddWorkspaceCustom.defaultProps = {
|
||||
picturePath: null,
|
||||
homeUrl: '',
|
||||
homeUrlError: null,
|
||||
internetIcon: null,
|
||||
name: '',
|
||||
nameError: null,
|
||||
picturePath: null,
|
||||
};
|
||||
|
||||
AddWorkspaceCustom.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
downloadingIcon: PropTypes.bool.isRequired,
|
||||
homeUrl: PropTypes.string,
|
||||
homeUrlError: PropTypes.string,
|
||||
internetIcon: PropTypes.string,
|
||||
isMailApp: PropTypes.bool.isRequired,
|
||||
name: PropTypes.string,
|
||||
nameError: PropTypes.string,
|
||||
onGetIconFromInternet: PropTypes.func.isRequired,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
onUpdateForm: PropTypes.func.isRequired,
|
||||
picturePath: PropTypes.string,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
downloadingIcon: state.addWorkspace.downloadingIcon,
|
||||
homeUrl: state.addWorkspace.form.homeUrl,
|
||||
homeUrlError: state.addWorkspace.form.homeUrlError,
|
||||
internetIcon: state.addWorkspace.form.internetIcon,
|
||||
isMailApp: Boolean(getMailtoUrl(state.addWorkspace.form.homeUrl)),
|
||||
name: state.addWorkspace.form.name,
|
||||
nameError: state.addWorkspace.form.nameError,
|
||||
|
|
@ -201,8 +240,9 @@ const mapStateToProps = (state) => ({
|
|||
});
|
||||
|
||||
const actionCreators = {
|
||||
updateForm,
|
||||
getIconFromInternet,
|
||||
save,
|
||||
updateForm,
|
||||
};
|
||||
|
||||
export default connectComponent(
|
||||
|
|
|
|||
|
|
@ -9,14 +9,18 @@ import ListItem from '@material-ui/core/ListItem';
|
|||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
|
||||
import Switch from '@material-ui/core/Switch';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
import connectComponent from '../../helpers/connect-component';
|
||||
import getMailtoUrl from '../../helpers/get-mailto-url';
|
||||
|
||||
import defaultIcon from '../../images/default-icon.png';
|
||||
|
||||
import { updateForm, save } from '../../state/edit-workspace/actions';
|
||||
|
||||
import {
|
||||
getIconFromInternet,
|
||||
save,
|
||||
updateForm,
|
||||
} from '../../state/edit-workspace/actions';
|
||||
|
||||
const styles = (theme) => ({
|
||||
root: {
|
||||
|
|
@ -43,19 +47,25 @@ const styles = (theme) => ({
|
|||
display: 'flex',
|
||||
},
|
||||
avatarLeft: {
|
||||
padding: theme.spacing.unit,
|
||||
paddingTop: theme.spacing.unit,
|
||||
paddingBottom: theme.spacing.unit,
|
||||
paddingLeft: 0,
|
||||
paddingRight: theme.spacing.unit,
|
||||
},
|
||||
avatarRight: {
|
||||
flex: 1,
|
||||
padding: theme.spacing.unit,
|
||||
paddingTop: theme.spacing.unit,
|
||||
paddingBottom: theme.spacing.unit,
|
||||
paddingLeft: theme.spacing.unit,
|
||||
paddingRight: 0,
|
||||
},
|
||||
avatar: {
|
||||
fontFamily: theme.typography.fontFamily,
|
||||
height: 64,
|
||||
width: 64,
|
||||
background: theme.palette.type === 'dark' ? theme.palette.common.white : theme.palette.common.black,
|
||||
background: theme.palette.type === 'dark' ? theme.palette.common.black : theme.palette.common.white,
|
||||
borderRadius: 4,
|
||||
color: theme.palette.getContrastText(theme.palette.type === 'dark' ? theme.palette.common.white : theme.palette.common.black),
|
||||
color: theme.palette.getContrastText(theme.palette.type === 'dark' ? theme.palette.common.black : theme.palette.common.white),
|
||||
fontSize: '32px',
|
||||
lineHeight: '64px',
|
||||
textAlign: 'center',
|
||||
|
|
@ -74,16 +84,29 @@ const styles = (theme) => ({
|
|||
},
|
||||
});
|
||||
|
||||
const getValidIconPath = (iconPath, internetIcon) => {
|
||||
if (iconPath) {
|
||||
return `file://${iconPath}`;
|
||||
}
|
||||
if (internetIcon) {
|
||||
return internetIcon;
|
||||
}
|
||||
return defaultIcon;
|
||||
};
|
||||
|
||||
const EditWorkspace = ({
|
||||
classes,
|
||||
disableAudio,
|
||||
disableNotifications,
|
||||
downloadingIcon,
|
||||
hibernateWhenUnused,
|
||||
homeUrl,
|
||||
homeUrlError,
|
||||
internetIcon,
|
||||
isMailApp,
|
||||
name,
|
||||
nameError,
|
||||
onGetIconFromInternet,
|
||||
onSave,
|
||||
onUpdateForm,
|
||||
picturePath,
|
||||
|
|
@ -124,18 +147,23 @@ const EditWorkspace = ({
|
|||
<div className={classes.avatarFlex}>
|
||||
<div className={classes.avatarLeft}>
|
||||
<div className={classes.avatar}>
|
||||
<img alt="Icon" className={classes.avatarPicture} src={picturePath ? `file://${picturePath}` : defaultIcon} />
|
||||
<img
|
||||
alt="Icon"
|
||||
className={classes.avatarPicture}
|
||||
src={getValidIconPath(picturePath, internetIcon)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.avatarRight}>
|
||||
<Button
|
||||
variant="contained"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
const { remote } = window.require('electron');
|
||||
const opts = {
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: 'Images', extensions: ['jpg', 'png'] },
|
||||
{ name: 'Images', extensions: ['png', 'jpg', 'jpeg'] },
|
||||
],
|
||||
};
|
||||
remote.dialog.showOpenDialog(remote.getCurrentWindow(), opts)
|
||||
|
|
@ -146,15 +174,29 @@ const EditWorkspace = ({
|
|||
});
|
||||
}}
|
||||
>
|
||||
Change Icon
|
||||
Select Local Image...
|
||||
</Button>
|
||||
<Typography variant="caption">
|
||||
PNG or JPEG.
|
||||
</Typography>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
className={classes.buttonBot}
|
||||
disabled={!homeUrl || homeUrlError || downloadingIcon}
|
||||
onClick={() => onGetIconFromInternet(true)}
|
||||
>
|
||||
{downloadingIcon ? 'Downloading Icon from the Internet...' : 'Download Icon from the Internet'}
|
||||
</Button>
|
||||
<br />
|
||||
<Button
|
||||
variant="contained"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
className={classes.buttonBot}
|
||||
onClick={() => onUpdateForm({ picturePath: null })}
|
||||
onClick={() => onUpdateForm({ picturePath: null, internetIcon: null })}
|
||||
disabled={!(picturePath || internetIcon)}
|
||||
>
|
||||
Remove Icon
|
||||
Reset to Default
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -201,21 +243,25 @@ const EditWorkspace = ({
|
|||
);
|
||||
|
||||
EditWorkspace.defaultProps = {
|
||||
picturePath: null,
|
||||
homeUrlError: null,
|
||||
internetIcon: null,
|
||||
nameError: null,
|
||||
picturePath: null,
|
||||
};
|
||||
|
||||
EditWorkspace.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
disableAudio: PropTypes.bool.isRequired,
|
||||
disableNotifications: PropTypes.bool.isRequired,
|
||||
downloadingIcon: PropTypes.bool.isRequired,
|
||||
hibernateWhenUnused: PropTypes.bool.isRequired,
|
||||
homeUrl: PropTypes.string.isRequired,
|
||||
homeUrlError: PropTypes.string,
|
||||
internetIcon: PropTypes.string,
|
||||
isMailApp: PropTypes.bool.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
nameError: PropTypes.string,
|
||||
onGetIconFromInternet: PropTypes.func.isRequired,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
onUpdateForm: PropTypes.func.isRequired,
|
||||
picturePath: PropTypes.string,
|
||||
|
|
@ -224,10 +270,12 @@ EditWorkspace.propTypes = {
|
|||
const mapStateToProps = (state) => ({
|
||||
disableAudio: Boolean(state.editWorkspace.form.disableAudio),
|
||||
disableNotifications: Boolean(state.editWorkspace.form.disableNotifications),
|
||||
downloadingIcon: state.editWorkspace.downloadingIcon,
|
||||
hibernateWhenUnused: Boolean(state.editWorkspace.form.hibernateWhenUnused),
|
||||
homeUrl: state.editWorkspace.form.homeUrl,
|
||||
homeUrlError: state.editWorkspace.form.homeUrlError,
|
||||
id: state.editWorkspace.form.id,
|
||||
internetIcon: state.editWorkspace.form.internetIcon,
|
||||
isMailApp: Boolean(getMailtoUrl(state.editWorkspace.form.homeUrl)),
|
||||
name: state.editWorkspace.form.name,
|
||||
nameError: state.editWorkspace.form.nameError,
|
||||
|
|
@ -236,6 +284,7 @@ const mapStateToProps = (state) => ({
|
|||
});
|
||||
|
||||
const actionCreators = {
|
||||
getIconFromInternet,
|
||||
updateForm,
|
||||
save,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -35,15 +35,19 @@ const styles = (theme) => ({
|
|||
avatar: {
|
||||
height: 32,
|
||||
width: 32,
|
||||
background: theme.palette.type === 'dark' ? theme.palette.common.white : theme.palette.common.black,
|
||||
background: theme.palette.type === 'dark' ? theme.palette.common.black : theme.palette.common.white,
|
||||
borderRadius: 4,
|
||||
color: theme.palette.getContrastText(theme.palette.type === 'dark' ? theme.palette.common.white : theme.palette.common.black),
|
||||
color: theme.palette.getContrastText(theme.palette.type === 'dark' ? theme.palette.common.black : theme.palette.common.white),
|
||||
lineHeight: '32px',
|
||||
textAlign: 'center',
|
||||
fontWeight: 500,
|
||||
textTransform: 'uppercase',
|
||||
boxShadow: theme.shadows[1],
|
||||
},
|
||||
addAvatar: {
|
||||
background: theme.palette.type === 'dark' ? theme.palette.common.white : theme.palette.common.black,
|
||||
color: theme.palette.getContrastText(theme.palette.type === 'dark' ? theme.palette.common.white : theme.palette.common.black),
|
||||
},
|
||||
avatarPicture: {
|
||||
height: 32,
|
||||
width: 32,
|
||||
|
|
@ -92,7 +96,7 @@ const WorkspaceSelector = ({
|
|||
onContextMenu={onContextMenu}
|
||||
tabIndex="0"
|
||||
>
|
||||
<div className={classes.avatar}>
|
||||
<div className={classNames(classes.avatar, id === 'add' && classes.addAvatar)}>
|
||||
{id !== 'add' ? (
|
||||
<img alt="Icon" className={classes.avatarPicture} src={picturePath ? `file://${picturePath}` : defaultIcon} draggable={false} />
|
||||
) : '+'}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const SET_WORKSPACE = 'SET_WORKSPACE';
|
|||
|
||||
// Edit Workspace
|
||||
export const UPDATE_EDIT_WORKSPACE_FORM = 'UPDATE_EDIT_WORKSPACE_FORM';
|
||||
export const UPDATE_EDIT_WORKSPACE_DOWNLOADING_ICON = 'UPDATE_EDIT_WORKSPACE_DOWNLOADING_ICON';
|
||||
|
||||
// General
|
||||
export const UPDATE_CAN_GO_BACK = 'UPDATE_CAN_GO_BACK';
|
||||
|
|
@ -48,9 +49,10 @@ export const ADD_WORKSPACE_GET_REQUEST = 'ADD_WORKSPACE_GET_REQUEST';
|
|||
export const ADD_WORKSPACE_GET_SUCCESS = 'ADD_WORKSPACE_GET_SUCCESS';
|
||||
export const ADD_WORKSPACE_RESET = 'ADD_WORKSPACE_RESET';
|
||||
export const ADD_WORKSPACE_UPDATE_CURRENT_QUERY = 'ADD_WORKSPACE_UPDATE_CURRENT_QUERY';
|
||||
export const ADD_WORKSPACE_UPDATE_QUERY = 'ADD_WORKSPACE_UPDATE_QUERY';
|
||||
export const ADD_WORKSPACE_UPDATE_DOWNLOADING_ICON = 'ADD_WORKSPACE_UPDATE_DOWNLOADING_ICON';
|
||||
export const ADD_WORKSPACE_UPDATE_FORM = 'ADD_WORKSPACE_UPDATE_FORM';
|
||||
export const ADD_WORKSPACE_UPDATE_MODE = 'ADD_WORKSPACE_UPDATE_MODE';
|
||||
export const ADD_WORKSPACE_UPDATE_QUERY = 'ADD_WORKSPACE_UPDATE_QUERY';
|
||||
|
||||
// License Registration
|
||||
export const LICENSE_REGISTRATION_FORM_UPDATE = 'LICENSE_REGISTRATION_FORM_UPDATE';
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ import {
|
|||
ADD_WORKSPACE_GET_SUCCESS,
|
||||
ADD_WORKSPACE_RESET,
|
||||
ADD_WORKSPACE_UPDATE_CURRENT_QUERY,
|
||||
ADD_WORKSPACE_UPDATE_QUERY,
|
||||
ADD_WORKSPACE_UPDATE_DOWNLOADING_ICON,
|
||||
ADD_WORKSPACE_UPDATE_FORM,
|
||||
ADD_WORKSPACE_UPDATE_MODE,
|
||||
ADD_WORKSPACE_UPDATE_QUERY,
|
||||
} from '../../constants/actions';
|
||||
|
||||
import validate from '../../helpers/validate';
|
||||
|
|
@ -19,7 +20,7 @@ import { requestCreateWorkspace } from '../../senders';
|
|||
const client = algoliasearch('OQ55YRVMNP', 'fc0fb115b113c21d58ed6a4b4de1565f');
|
||||
const index = client.initIndex('apps');
|
||||
|
||||
const { remote } = window.require('electron');
|
||||
const { ipcRenderer, remote } = window.require('electron');
|
||||
|
||||
export const getHits = () => (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
|
@ -106,11 +107,70 @@ const getValidationRules = () => ({
|
|||
},
|
||||
});
|
||||
|
||||
export const updateForm = (changes) => ({
|
||||
type: ADD_WORKSPACE_UPDATE_FORM,
|
||||
changes: validate(changes, getValidationRules()),
|
||||
// to be replaced with invoke (electron 7+)
|
||||
// https://electronjs.org/docs/api/ipc-renderer#ipcrendererinvokechannel-args
|
||||
export const getWebsiteIconUrlAsync = (url) => new Promise((resolve, reject) => {
|
||||
try {
|
||||
const id = Date.now().toString();
|
||||
ipcRenderer.once(id, (e, uurl) => {
|
||||
resolve(uurl);
|
||||
});
|
||||
ipcRenderer.send('request-get-website-icon-url', id, url);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
|
||||
export const getIconFromInternet = (forceOverwrite) => (dispatch, getState) => {
|
||||
const { form: { picturePath, homeUrl, homeUrlError } } = getState().addWorkspace;
|
||||
if ((!forceOverwrite && picturePath) || !homeUrl || homeUrlError) return;
|
||||
|
||||
dispatch({
|
||||
type: ADD_WORKSPACE_UPDATE_DOWNLOADING_ICON,
|
||||
downloadingIcon: true,
|
||||
});
|
||||
|
||||
getWebsiteIconUrlAsync(homeUrl)
|
||||
.then((iconUrl) => {
|
||||
const { form } = getState().addWorkspace;
|
||||
if (form.homeUrl === homeUrl) {
|
||||
const changes = { internetIcon: iconUrl || form.internetIcon };
|
||||
if (forceOverwrite) changes.picturePath = null;
|
||||
dispatch(({
|
||||
type: ADD_WORKSPACE_UPDATE_FORM,
|
||||
changes,
|
||||
}));
|
||||
dispatch({
|
||||
type: ADD_WORKSPACE_UPDATE_DOWNLOADING_ICON,
|
||||
downloadingIcon: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (forceOverwrite && !iconUrl) {
|
||||
remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
message: 'Unable to find a suitable icon from the Internet.',
|
||||
buttons: ['OK'],
|
||||
cancelId: 0,
|
||||
defaultId: 0,
|
||||
});
|
||||
}
|
||||
}).catch(console.log); // eslint-disable-line no-console
|
||||
};
|
||||
|
||||
let timeout2;
|
||||
export const updateForm = (changes) => (dispatch) => {
|
||||
dispatch({
|
||||
type: ADD_WORKSPACE_UPDATE_FORM,
|
||||
changes: validate(changes, getValidationRules()),
|
||||
});
|
||||
|
||||
clearTimeout(timeout2);
|
||||
timeout2 = setTimeout(() => {
|
||||
if (changes.internetIcon === null) return; // user explictly want to get rid of icon
|
||||
dispatch(getIconFromInternet());
|
||||
}, 300);
|
||||
};
|
||||
|
||||
export const save = () => (dispatch, getState) => {
|
||||
const { form } = getState().addWorkspace;
|
||||
|
||||
|
|
@ -119,7 +179,7 @@ export const save = () => (dispatch, getState) => {
|
|||
return dispatch(updateForm(validatedChanges));
|
||||
}
|
||||
|
||||
requestCreateWorkspace(form.name, form.homeUrl.trim(), form.picturePath);
|
||||
requestCreateWorkspace(form.name, form.homeUrl.trim(), form.internetIcon || form.picturePath);
|
||||
remote.getCurrentWindow().close();
|
||||
return null;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ import {
|
|||
ADD_WORKSPACE_GET_SUCCESS,
|
||||
ADD_WORKSPACE_RESET,
|
||||
ADD_WORKSPACE_UPDATE_CURRENT_QUERY,
|
||||
ADD_WORKSPACE_UPDATE_QUERY,
|
||||
ADD_WORKSPACE_UPDATE_DOWNLOADING_ICON,
|
||||
ADD_WORKSPACE_UPDATE_FORM,
|
||||
ADD_WORKSPACE_UPDATE_MODE,
|
||||
ADD_WORKSPACE_UPDATE_QUERY,
|
||||
} from '../../constants/actions';
|
||||
|
||||
const hasFailed = (state = false, action) => {
|
||||
|
|
@ -87,14 +88,22 @@ const mode = (state = defaultMode, action) => {
|
|||
}
|
||||
};
|
||||
|
||||
const downloadingIcon = (state = false, action) => {
|
||||
switch (action.type) {
|
||||
case ADD_WORKSPACE_UPDATE_DOWNLOADING_ICON: return action.downloadingIcon;
|
||||
default: return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default combineReducers({
|
||||
currentQuery,
|
||||
downloadingIcon,
|
||||
form,
|
||||
hasFailed,
|
||||
hits,
|
||||
isGetting,
|
||||
mode,
|
||||
page,
|
||||
query,
|
||||
totalPage,
|
||||
form,
|
||||
mode,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
|
||||
import { UPDATE_EDIT_WORKSPACE_FORM } from '../../constants/actions';
|
||||
import {
|
||||
UPDATE_EDIT_WORKSPACE_DOWNLOADING_ICON,
|
||||
UPDATE_EDIT_WORKSPACE_FORM,
|
||||
} from '../../constants/actions';
|
||||
|
||||
import validate from '../../helpers/validate';
|
||||
import hasErrors from '../../helpers/has-errors';
|
||||
|
|
@ -10,7 +13,7 @@ import {
|
|||
requestRemoveWorkspacePicture,
|
||||
} from '../../senders';
|
||||
|
||||
const { remote } = window.require('electron');
|
||||
const { ipcRenderer, remote } = window.require('electron');
|
||||
|
||||
const getValidationRules = () => ({
|
||||
name: {
|
||||
|
|
@ -24,11 +27,63 @@ const getValidationRules = () => ({
|
|||
},
|
||||
});
|
||||
|
||||
export const updateForm = (changes) => ({
|
||||
type: UPDATE_EDIT_WORKSPACE_FORM,
|
||||
changes: validate(changes, getValidationRules()),
|
||||
// to be replaced with invoke (electron 7+)
|
||||
// https://electronjs.org/docs/api/ipc-renderer#ipcrendererinvokechannel-args
|
||||
export const getWebsiteIconUrlAsync = (url) => new Promise((resolve, reject) => {
|
||||
try {
|
||||
const id = Date.now().toString();
|
||||
ipcRenderer.once(id, (e, uurl) => {
|
||||
resolve(uurl);
|
||||
});
|
||||
ipcRenderer.send('request-get-website-icon-url', id, url);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
|
||||
export const getIconFromInternet = (forceOverwrite) => (dispatch, getState) => {
|
||||
const { form: { picturePath, homeUrl, homeUrlError } } = getState().editWorkspace;
|
||||
if ((!forceOverwrite && picturePath) || !homeUrl || homeUrlError) return;
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_EDIT_WORKSPACE_DOWNLOADING_ICON,
|
||||
downloadingIcon: true,
|
||||
});
|
||||
|
||||
getWebsiteIconUrlAsync(homeUrl)
|
||||
.then((iconUrl) => {
|
||||
const { form } = getState().editWorkspace;
|
||||
if (form.homeUrl === homeUrl) {
|
||||
const changes = { internetIcon: iconUrl || form.internetIcon };
|
||||
if (forceOverwrite) changes.picturePath = null;
|
||||
dispatch(({
|
||||
type: UPDATE_EDIT_WORKSPACE_FORM,
|
||||
changes,
|
||||
}));
|
||||
dispatch({
|
||||
type: UPDATE_EDIT_WORKSPACE_DOWNLOADING_ICON,
|
||||
downloadingIcon: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (forceOverwrite && !iconUrl) {
|
||||
remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
message: 'Unable to find a suitable icon from the Internet.',
|
||||
buttons: ['OK'],
|
||||
cancelId: 0,
|
||||
defaultId: 0,
|
||||
});
|
||||
}
|
||||
}).catch(console.log); // eslint-disable-line no-console
|
||||
};
|
||||
|
||||
export const updateForm = (changes) => (dispatch) => {
|
||||
dispatch({
|
||||
type: UPDATE_EDIT_WORKSPACE_FORM,
|
||||
changes: validate(changes, getValidationRules()),
|
||||
});
|
||||
};
|
||||
|
||||
export const save = () => (dispatch, getState) => {
|
||||
const { form } = getState().editWorkspace;
|
||||
|
||||
|
|
@ -53,6 +108,8 @@ export const save = () => (dispatch, getState) => {
|
|||
|
||||
if (form.picturePath) {
|
||||
requestSetWorkspacePicture(id, form.picturePath);
|
||||
} else if (form.internetIcon) {
|
||||
requestSetWorkspacePicture(id, form.internetIcon);
|
||||
} else {
|
||||
requestRemoveWorkspacePicture(id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { combineReducers } from 'redux';
|
||||
|
||||
import { UPDATE_EDIT_WORKSPACE_FORM } from '../../constants/actions';
|
||||
import {
|
||||
UPDATE_EDIT_WORKSPACE_DOWNLOADING_ICON,
|
||||
UPDATE_EDIT_WORKSPACE_FORM,
|
||||
} from '../../constants/actions';
|
||||
|
||||
import { getWorkspaces } from '../../senders';
|
||||
import getWorkspacesAsList from '../../helpers/get-workspaces-as-list';
|
||||
|
|
@ -28,4 +31,12 @@ const form = (state = defaultForm, action) => {
|
|||
}
|
||||
};
|
||||
|
||||
export default combineReducers({ form });
|
||||
const downloadingIcon = (state = false, action) => {
|
||||
switch (action.type) {
|
||||
case UPDATE_EDIT_WORKSPACE_DOWNLOADING_ICON: return action.downloadingIcon;
|
||||
default: return state;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export default combineReducers({ downloadingIcon, form });
|
||||
|
|
|
|||
37
yarn.lock
37
yarn.lock
|
|
@ -3704,6 +3704,18 @@ chardet@^0.7.0:
|
|||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
|
||||
|
||||
cheerio@1.0.0-rc.3:
|
||||
version "1.0.0-rc.3"
|
||||
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6"
|
||||
integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==
|
||||
dependencies:
|
||||
css-select "~1.2.0"
|
||||
dom-serializer "~0.1.1"
|
||||
entities "~1.1.1"
|
||||
htmlparser2 "^3.9.1"
|
||||
lodash "^4.15.0"
|
||||
parse5 "^3.0.1"
|
||||
|
||||
chokidar@^2.0.2, chokidar@^2.0.4, chokidar@^2.1.8:
|
||||
version "2.1.8"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
|
||||
|
|
@ -4327,7 +4339,7 @@ css-select-base-adapter@^0.1.1:
|
|||
resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7"
|
||||
integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==
|
||||
|
||||
css-select@^1.1.0:
|
||||
css-select@^1.1.0, css-select@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"
|
||||
integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=
|
||||
|
|
@ -4858,6 +4870,14 @@ dom-serializer@0:
|
|||
domelementtype "^2.0.1"
|
||||
entities "^2.0.0"
|
||||
|
||||
dom-serializer@~0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0"
|
||||
integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==
|
||||
dependencies:
|
||||
domelementtype "^1.3.0"
|
||||
entities "^1.1.1"
|
||||
|
||||
dom-walk@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
|
||||
|
|
@ -4868,7 +4888,7 @@ domain-browser@^1.1.1:
|
|||
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
|
||||
integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
|
||||
|
||||
domelementtype@1, domelementtype@^1.3.1:
|
||||
domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
|
||||
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
|
||||
|
|
@ -5164,7 +5184,7 @@ enhanced-resolve@^4.1.0:
|
|||
memory-fs "^0.5.0"
|
||||
tapable "^1.0.0"
|
||||
|
||||
entities@^1.1.1:
|
||||
entities@^1.1.1, entities@~1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
|
||||
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
|
||||
|
|
@ -6696,7 +6716,7 @@ html-webpack-plugin@4.0.0-beta.5:
|
|||
tapable "^1.1.0"
|
||||
util.promisify "1.0.0"
|
||||
|
||||
htmlparser2@^3.3.0:
|
||||
htmlparser2@^3.3.0, htmlparser2@^3.9.1:
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
|
||||
integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
|
||||
|
|
@ -8394,7 +8414,7 @@ lodash.uniq@^4.5.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
||||
|
||||
"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5:
|
||||
"lodash@>=3.5 <5", lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
|
|
@ -9545,6 +9565,13 @@ parse5@5.1.0:
|
|||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2"
|
||||
integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==
|
||||
|
||||
parse5@^3.0.1:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c"
|
||||
integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
parseurl@~1.3.2, parseurl@~1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue