1 import React, { Component } from 'react';
3 import Window from './Components/Window';
4 import WindowButtons from './Components/WindowButtons';
5 import WindowButton from './Components/WindowButton';
6 import WindowContent from './Components/WindowContent';
7 import TitleBar from './Components/TitleBar';
8 import Tabs from './Components/Tabs';
9 import { TabContainer, Tab, TabIcon, TabTitle, TabCloseButton } from './Components/Tab';
10 import TabButton from './Components/TabButton';
11 import TabContent from './Components/TabContent';
12 import ToolBar from './Components/ToolBar';
13 import ToolBarButton from './Components/ToolBarButton';
14 import ToolBarTextBox from './Components/ToolBarTextBox';
15 import ContentWrapper from './Components/ContentWrapper';
17 import WindowMinimizeIcon from './Resources/windows/minimize.svg';
18 import WindowMaximizeIcon from './Resources/windows/maximize.svg';
19 import WindowCloseIcon from './Resources/windows/close.svg';
21 import BackIcon from './Resources/arrow_back.svg';
22 import ForwardIcon from './Resources/arrow_forward.svg';
23 import BackInActiveIcon from './Resources/arrow_back_inactive.svg';
24 import ForwardInActiveIcon from './Resources/arrow_forward_inactive.svg';
25 import ReloadIcon from './Resources/reload.svg';
26 import HomeIcon from './Resources/home.svg';
27 import AccountIcon from './Resources/account.svg';
28 import MoreIcon from './Resources/more.svg';
29 import PublicIcon from './Resources/public.svg';
30 import PublicInActiveIcon from './Resources/public_inactive.svg';
31 import ShieldIcon from './Resources/shield.svg';
32 import AddIcon from './Resources/add.svg';
33 import CloseIcon from './Resources/close.svg';
35 import isURL from './Utils/isURL';
37 const { remote, ipcRenderer, shell } = window.require('electron');
38 const systemPreferences = remote.systemPreferences;
39 const Menu = remote.Menu;
40 const MenuItem = remote.MenuItem;
41 const dialog = remote.dialog;
43 const platform = window.require('electron-platform');
44 const process = window.require('process');
46 const Config = window.require('electron-store');
47 const config = new Config({
55 defaultPage: 'my://newtab',
56 defaultEngine: 'Google',
60 url: 'https://www.google.com/search?q=%s'
64 url: 'https://www.bing.com/search?q=%s'
68 url: 'https://search.yahoo.co.jp/search?p=%s'
72 url: 'https://search.goo.ne.jp/web.jsp?MT=%s'
75 name: 'Google Translate',
76 url: 'https://translate.google.com/?text=%s'
80 url: 'https://www.youtube.com/results?search_query=%s'
84 url: 'https://www.twitter.com/search?q=%s'
88 url: 'https://github.com/search?q=%s'
94 isCustomTitlebar: true,
104 class BrowserView extends Component {
119 componentDidMount() {
121 let webView = this.refs.webView;
122 let props = this.props;
124 webView.addEventListener('did-finish-load', (e) => {
125 document.title = webView.getTitle();
126 this.setText(webView.getURL());
127 this.props.updateTab(webView.getTitle(), PublicIcon, webView.getURL(), this.props.index);
128 this.setState({ barText: webView.getURL() });
131 webView.addEventListener('page-title-updated', (e) => {
132 document.title = webView.getTitle();
133 this.setText(webView.getURL());
134 this.props.updateTab(webView.getTitle(), PublicIcon, webView.getURL(), this.props.index);
135 this.setState({ barText: webView.getURL() });
138 webView.addEventListener('page-favicon-updated', (e) => {
139 if (e.favicons.length > 0) {
140 this.props.updateTab(webView.getTitle(), e.favicons[0], webView.getURL(), this.props.index);
142 this.props.updateTab(webView.getTitle(), PublicIcon, webView.getURL(), this.props.index);
146 webView.addEventListener('load-commit', (e) => {
148 document.title = webView.getTitle();
149 this.setText(webView.getURL());
150 this.props.updateTab(webView.getTitle(), PublicIcon, webView.getURL(), this.props.index);
151 this.setState({ barText: webView.getURL() });
155 webView.addEventListener('found-in-page', (e, result) => {
156 if (result.activeMatchOrdinal) {
158 activeMatch: result.activeMatchOrdinal,
159 resultString: `${this.state.activeMatch}/${result.matches}`
163 if (result.finalUpdate) {
164 this.setState({ resultString: `${this.state.activeMatch}/${result.matches}` });
168 webView.addEventListener('new-window', (e, url) => {
169 this.props.addTab('新しいタブ', '', e.url);
173 ipcRenderer.on(`browserview-start-loading-${this.props.windowId}`, (e, args) => {
174 if (args.id == this.props.index) {
175 this.setState({ isLoading: true });
179 ipcRenderer.on(`browserview-stop-loading-${this.props.windowId}`, (e, args) => {
180 if (args.id == this.props.index) {
181 this.setState({ isLoading: false });
185 ipcRenderer.on(`browserview-load-${this.props.windowId}`, (e, args) => {
186 if (args.id == this.props.index) {
187 this.props.updateTab();
188 this.setText(args.url);
192 ipcRenderer.on(`update-navigation-state-${this.props.windowId}`, (e, args) => {
193 if (args.id == this.props.index) {
194 this.setState({ canGoBack: args.canGoBack, canGoForward: args.canGoForward });
198 // console.log(webView);
201 handleKeyDown = (e) => {
202 if (e.key != 'Enter' || this.state.barText.length == 0 || this.state.barText == '') return;
204 if (isURL(this.state.barText)) {
205 const pattern = /^(file:\/\/\S.*)\S*$/;
207 if (pattern.test(this.state.barText)) {
208 ipcRenderer.send(`browserview-loadFile-${this.props.windowId}`, { id: this.props.index, url: this.state.barText.replace('file://', '') });
210 ipcRenderer.send(`browserview-loadURL-${this.props.windowId}`, { id: this.props.index, url: this.state.barText });
213 ipcRenderer.send(`browserview-loadURL-${this.props.windowId}`, { id: this.props.index, url: this.getSearchEngine().url.replace('%s', this.state.barText) });
217 handleContextMenu = (e) => {
218 const menu = Menu.buildFromTemplate([
221 accelerator: 'CmdOrCtrl+Z',
226 accelerator: 'CmdOrCtrl+Y',
229 { type: 'separator' },
232 accelerator: 'CmdOrCtrl+X',
237 accelerator: 'CmdOrCtrl+C',
242 accelerator: 'CmdOrCtrl+V',
245 { type: 'separator' },
248 accelerator: 'CmdOrCtrl+A',
255 getSearchEngine = () => {
256 for (var i = 0; i < config.get('homePage.searchEngines').length; i++) {
257 if (config.get('homePage.searchEngines')[i].name == config.get('homePage.defaultEngine')) {
258 return config.get('homePage.searchEngines')[i];
263 setText = (text) => {
264 this.setState({ barText: text });
268 ipcRenderer.send(`browserview-goBack-${this.props.windowId}`, { id: this.props.index });
272 ipcRenderer.send(`browserview-goForward-${this.props.windowId}`, { id: this.props.index });
276 if (!this.state.isLoading) {
277 ipcRenderer.send(`browserview-reload-${this.props.windowId}`, { id: this.props.index });
279 ipcRenderer.send(`browserview-stop-${this.props.windowId}`, { id: this.props.index });
284 ipcRenderer.send(`browserview-goHome-${this.props.windowId}`, { id: this.props.index });
287 userMenu = async () => {
288 const menu = Menu.buildFromTemplate([
290 label: (!this.props.windowId.startsWith('private') ? `${process.env.USERNAME} でログイン中` : 'プライベートモード'),
293 { type: 'separator' },
296 click: () => { remote.getCurrentWindow().close(); }
300 x: remote.getCurrentWindow().getSize()[0] - 57,
306 const menu = Menu.buildFromTemplate([
309 accelerator: 'CmdOrCtrl+T',
310 click: () => { this.props.addTab('https://www.google.co.jp/'); }
314 accelerator: 'CmdOrCtrl+N',
315 click: () => { ipcRenderer.send(`window-add`, { isPrivate: false }); }
318 label: 'シークレット ウィンドウを開く',
319 accelerator: 'CmdOrCtrl+Shift+N',
320 click: () => { ipcRenderer.send(`window-add`, { isPrivate: true }); }
322 { type: 'separator' },
325 click: () => { this.props.addTab('my://history'); }
329 click: () => { this.props.addTab('my://history'); }
333 click: () => { this.props.addTab('my://history'); }
335 { type: 'separator' },
338 click: () => { this.props.addTab('my://settings'); }
342 click: () => { this.props.addTab('my://help'); }
344 { type: 'separator' },
347 click: () => { remote.getCurrentWindow().close(); }
351 x: remote.getCurrentWindow().getSize()[0] - 24,
360 <ToolBarButton isShowing={true} isRight={false} isMarginLeft={true} isEnabled={this.state.canGoBack} src={this.state.canGoBack ? BackIcon : BackInActiveIcon} size={24} title="前のページに戻る" onClick={() => { this.goBack(); }} />
361 <ToolBarButton isShowing={true} isRight={false} isMarginLeft={false} isEnabled={this.state.canGoForward} src={this.state.canGoForward ? ForwardIcon : ForwardInActiveIcon} size={24} title="次のページに進む" onClick={() => { this.goForward(); }} />
362 <ToolBarButton isShowing={true} isRight={false} isMarginLeft={false} isEnabled={true} src={!this.state.isLoading ? ReloadIcon : CloseIcon} size={24} title={!this.state.isLoading ? '再読み込み' : '読み込み中止'} onClick={() => { this.reload(); }} />
363 <ToolBarButton isShowing={config.get('design.homeButton')} isRight={false} isMarginLeft={false} isEnabled={true} src={HomeIcon} size={24} title="ホームページに移動" onClick={() => { this.goHome(); }} />
364 <ToolBarTextBox buttonCount={config.get('design.homeButton') ? 5 : 4} value={this.state.barText} onChange={(e) => { this.setState({ barText: e.target.value }); }} onKeyDown={this.handleKeyDown} onContextMenu={this.handleContextMenu} />
365 <ToolBarButton isShowing={true} isRight={true} isMarginLeft={true} isEnabled={true} src={!this.props.windowId.startsWith('private') ? AccountIcon : ShieldIcon} size={24} title={!this.props.windowId.startsWith('private') ? process.env.USERNAME : 'プライベートモード'} onClick={() => { this.userMenu(); }} />
366 <ToolBarButton isShowing={true} isRight={true} isMarginLeft={false} isEnabled={true} src={MoreIcon} size={24} title="メニュー" onClick={() => { this.moreMenu(); }} />
373 class App extends Component {
382 ipcRenderer.on(`browserview-get-${this.props.match.params.windowId}`, (e, args) => {
383 this.setState({ tabs: args.views });
386 ipcRenderer.on(`browserview-set-${this.props.match.params.windowId}`, (e, args) => {
387 this.setState({ current: args.id });
391 componentDidMount = () => {
395 handleContextMenu = (i) => {
397 const menu = Menu.buildFromTemplate([
400 accelerator: 'CmdOrCtrl+T',
401 click: function () { t.addTab('https://www.google.co.jp/'); }
405 accelerator: 'CmdOrCtrl+W',
406 click: function () { t.removeTab(i); }
408 { type: 'separator' },
412 checked: t.state.tabs[i].fixed,
414 var newTabs = t.state.tabs.concat();
415 newTabs[i].fixed = !t.state.tabs[i].fixed;
416 t.setState({ tabs: newTabs });
420 menu.popup(remote.getCurrentWindow());
424 ipcRenderer.send(`browserview-get-${this.props.match.params.windowId}`, {});
427 addTab = (url = config.get('homePage.defaultPage')) => {
428 ipcRenderer.send(`browserview-add-${this.props.match.params.windowId}`, { url, isActive: true });
429 ipcRenderer.send(`browserview-get-${this.props.match.params.windowId}`, {});
430 this.setState({ current: this.state.tabs.length });
434 ipcRenderer.send(`browserview-remove-${this.props.match.params.windowId}`, { id: i });
435 ipcRenderer.send(`browserview-get-${this.props.match.params.windowId}`, {});
437 if ((this.state.tabs.length - 1) > 0) {
438 const id = this.state.current--;
439 ipcRenderer.send(`browserview-select-${this.props.match.params.windowId}`, { id: id });
441 remote.getCurrentWindow().close();
446 ipcRenderer.send(`browserview-get-${this.props.match.params.windowId}`, {});
449 getForegroundColor = (hexColor) => {
450 var r = parseInt(hexColor.substr(1, 2), 16);
451 var g = parseInt(hexColor.substr(3, 2), 16);
452 var b = parseInt(hexColor.substr(5, 2), 16);
454 return ((((r * 299) + (g * 587) + (b * 114)) / 1000) < 128) ? '#ffffff' : '#000000';
459 <Window isCustomTitlebar={config.get('window.isCustomTitlebar')}>
460 <TitleBar color={platform.isWin32 || platform.isDarwin ? `#${systemPreferences.getAccentColor()}` : '#353535'}>
461 <Tabs isCustomTitlebar={config.get('window.isCustomTitlebar')}>
463 {this.state.tabs.map((tab, i) => {
465 <Tab isActive={tab.id == this.state.current} isFixed={tab.fixed} onClick={() => { ipcRenderer.send(`browserview-select-${this.props.match.params.windowId}`, { id: tab.id }); }} onContextMenu={this.handleContextMenu.bind(this, tab.id)}>
466 <TabIcon src={tab.icon != undefined ? tab.icon : PublicIcon} width={18} height={18} />
467 <TabTitle isFixed={tab.fixed} title={tab.title}>{tab.title}</TabTitle>
468 <TabCloseButton isActive={tab.id == this.state.current} isFixed={tab.fixed} isRight={true} src={CloseIcon} size={14} title="このタブを閉じる" onClick={() => { this.removeTab(tab.id); }} />
473 <TabButton isRight={true} src={AddIcon} size={24} title="新しいタブを開く" onClick={() => { this.addTab(); }} />
475 <WindowButtons isCustomTitlebar={config.get('window.isCustomTitlebar')}>
476 <WindowButton isClose={false} title="最小化" onClick={() => { remote.getCurrentWindow().minimize(); }}>
477 <svg name="TitleBarMinimize" width="12" height="12" viewBox="0 0 12 12">
478 <rect fill={this.getForegroundColor(platform.isWin32 || platform.isDarwin ? `#${systemPreferences.getAccentColor()}` : '#353535')} width="10" height="1" x="1" y="6" />
481 <WindowButton isClose={false} title={remote.getCurrentWindow().isMaximized() ? '元に戻す (縮小)' : '最大化'} onClick={() => { remote.getCurrentWindow().isMaximized() ? remote.getCurrentWindow().unmaximize() : remote.getCurrentWindow().maximize(); }}>
482 <svg name="TitleBarMaximize" width="12" height="12" viewBox="0 0 12 12">
483 <rect stroke={this.getForegroundColor(platform.isWin32 || platform.isDarwin ? `#${systemPreferences.getAccentColor()}` : '#353535')} width="9" height="9" x="1.5" y="1.5" fill="none" />
486 <WindowButton isClose={true} title="閉じる" onClick={() => { remote.getCurrentWindow().close(); }}>
487 <svg name="TitleBarClose" width="12" height="12" viewBox="0 0 12 12">
488 <polygon fill={this.getForegroundColor(platform.isWin32 || platform.isDarwin ? `#${systemPreferences.getAccentColor()}` : '#353535')} fill-rule="evenodd" points="11 1.576 6.583 6 11 10.424 10.424 11 6 6.583 1.576 11 1 10.424 5.417 6 1 1.576 1.576 1 6 5.417 10.424 1" />
494 {this.state.tabs.map((tab, i) => {
496 <TabContent isActive={tab.id == this.state.current}>
497 <BrowserView key={i} windowId={this.props.match.params.windowId} index={tab.id} url={tab.url}
498 addTab={(url) => { this.addTab(url); }}
499 updateTab={() => { this.updateTab(); }} />