This commit is contained in:
crusader 2018-08-15 20:14:23 +09:00
parent 7111e03049
commit 8f4d7ec895
9 changed files with 537 additions and 23 deletions

View File

@ -8,6 +8,7 @@
2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z" 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"
/> />
</svg> </svg>
<of-app-menu-bar></of-app-menu-bar> <of-app-menu-bar></of-app-menu-bar>
<of-window-controls></of-window-controls> <of-window-controls></of-window-controls>
</div> </div>

View File

@ -1,24 +1,22 @@
<div class="window-controls"> <button aria-label="minimize" tabindex="-1" class="window-control minimize" (click)="onMinimize($event)">
<button aria-label="minimize" tabindex="-1" class="window-control minimize" (click)="onMinimize($event)"> <svg aria-hidden="true" version="1.1" width="10" height="10">
<path d="M 0,5 10,5 10,6 0,6 Z" />
</svg>
</button>
<ng-container [ngSwitch]="windowState">
<button *ngSwitchCase="maximized" aria-label="restore" tabindex="-1" class="window-control restore" (click)="onRestore($event)">
<svg aria-hidden="true" version="1.1" width="10" height="10"> <svg aria-hidden="true" version="1.1" width="10" height="10">
<path d="M 0,5 10,5 10,6 0,6 Z" /> <path d="m 2,1e-5 0,2 -2,0 0,8 8,0 0,-2 2,0 0,-8 z m 1,1 6,0 0,6 -1,0 0,-5 -5,0 z m -2,2 6,0 0,6 -6,0 z" />
</svg> </svg>
</button> </button>
<ng-container [ngSwitch]="windowState"> <button *ngSwitchDefault aria-label="maximize" tabindex="-1" class="window-control maximize" (click)="onMaximize($event)">
<button *ngSwitchCase="maximized" aria-label="restore" tabindex="-1" class="window-control restore" (click)="onRestore($event)">
<svg aria-hidden="true" version="1.1" width="10" height="10">
<path d="m 2,1e-5 0,2 -2,0 0,8 8,0 0,-2 2,0 0,-8 z m 1,1 6,0 0,6 -1,0 0,-5 -5,0 z m -2,2 6,0 0,6 -6,0 z" />
</svg>
</button>
<button *ngSwitchDefault aria-label="maximize" tabindex="-1" class="window-control maximize" (click)="onMaximize($event)">
<svg aria-hidden="true" version="1.1" width="10" height="10">
<path d="M 0,0 0,10 10,10 10,0 Z M 1,1 9,1 9,9 1,9 Z" />
</svg>
</button>
</ng-container>
<button aria-label="close" tabindex="-1" class="window-control close" (click)="onClose($event)">
<svg aria-hidden="true" version="1.1" width="10" height="10"> <svg aria-hidden="true" version="1.1" width="10" height="10">
<path d="M 0,0 0,0.7 4.3,5 0,9.3 0,10 0.7,10 5,5.7 9.3,10 10,10 10,9.3 5.7,5 10,0.7 10,0 9.3,0 5,4.3 0.7,0 Z" /> <path d="M 0,0 0,10 10,10 10,0 Z M 1,1 9,1 9,9 1,9 Z" />
</svg> </svg>
</button> </button>
</div> </ng-container>
<button aria-label="close" tabindex="-1" class="window-control close" (click)="onClose($event)">
<svg aria-hidden="true" version="1.1" width="10" height="10">
<path d="M 0,0 0,0.7 4.3,5 0,9.3 0,10 0.7,10 5,5.7 9.3,10 10,10 10,9.3 5.7,5 10,0.7 10,0 9.3,0 5,4.3 0.7,0 Z" />
</svg>
</button>

26
config/app-info.js Normal file
View File

@ -0,0 +1,26 @@
const fse = require('fs-extra');
const path = require('path');
const { getUpdatesURL, getReleaseChannel } = require('./distribution-info');
const projectRoot = path.dirname(__dirname, '..');
const channel = getReleaseChannel();
const s = JSON.stringify;
function getReplacements() {
return {
__DARWIN__: process.platform === 'darwin',
__WIN32__: process.platform === 'win32',
__LINUX__: process.platform === 'linux',
__DEV__: channel === 'development',
__RELEASE_CHANNEL__: s(channel),
__UPDATES_URL__: s(getUpdatesURL()),
'process.platform': s(process.platform),
'process.env.NODE_ENV': s(process.env.NODE_ENV || 'development'),
'process.env.TEST_ENV': s(process.env.TEST_ENV),
}
}
exports.getReplacements = getReplacements;

157
config/distribution-info.js Normal file
View File

@ -0,0 +1,157 @@
const fse = require('fs-extra');
const path = require('path');
const { getProductName, getVersion } = require('./package-info');
const productName = getProductName();
const version = getVersion();
const projectRoot = path.join(__dirname, '..');
function getDistRoot() {
return path.join(projectRoot, 'build', 'dist');
}
function getDistPath() {
return path.join(
getDistRoot(),
`${getExecutableName()}-${process.platform}-x64`
);
}
function getExecutableName() {
const suffix = process.env.NODE_ENV === 'development' ? '-dev' : '';
if (process.platform === 'win32') {
return `${getWindowsIdentifierName()}${suffix}`;
} else if (process.platform === 'linux') {
return 'desktop';
} else {
return productName;
}
}
function getOSXZipName() {
return `${productName}.zip`;
}
function getOSXZipPath() {
return path.join(getDistPath(), '..', getOSXZipName());
}
function getWindowsInstallerName() {
const productName = getExecutableName();
return `${productName}Setup.msi`;
}
function getWindowsInstallerPath() {
return path.join(getDistPath(), '..', 'installer', getWindowsInstallerName());
}
function getWindowsStandaloneName() {
const productName = getExecutableName();
return `${productName}Setup.exe`;
}
function getWindowsStandalonePath() {
return path.join(getDistPath(), '..', 'installer', getWindowsStandaloneName());
}
function getWindowsFullNugetPackageName() {
return `${getWindowsIdentifierName()}-${version}-full.nupkg`;
}
function getWindowsFullNugetPackagePath() {
return path.join(
getDistPath(),
'..',
'installer',
getWindowsFullNugetPackageName()
);
}
function getWindowsDeltaNugetPackageName() {
return `${getWindowsIdentifierName()}-${version}-delta.nupkg`;
}
function getWindowsDeltaNugetPackagePath() {
return path.join(
getDistPath(),
'..',
'installer',
getWindowsDeltaNugetPackageName()
);
}
function getWindowsIdentifierName() {
return 'GitHubDesktop';
}
function getBundleSizes() {
// eslint-disable-next-line no-sync
const rendererStats = fse.statSync(
path.join(projectRoot, 'out', 'renderer.js')
);
// eslint-disable-next-line no-sync
const mainStats = fse.statSync(path.join(projectRoot, 'out', 'main.js'));
return { rendererSize: rendererStats.size, mainSize: mainStats.size };
}
function getReleaseBranchName() {
return (
process.env.CIRCLE_BRANCH || // macOS
process.env.APPVEYOR_REPO_BRANCH || // Windows
''
);
}
function getReleaseChannel() {
// Branch name format: __release-CHANNEL-DEPLOY_ID
const pieces = getReleaseBranchName().split('-');
if (pieces.length < 3 || pieces[0] !== '__release') {
return process.env.NODE_ENV || 'development';
}
return pieces[1];
}
function getReleaseSHA() {
// Branch name format: __release-CHANNEL-DEPLOY_ID
const pieces = getReleaseBranchName().split('-');
if (pieces.length < 3 || pieces[0] !== '__release') {
return null;
}
return pieces[2];
}
function getUpdatesURL() {
return `https://central.github.com/api/deployments/desktop/desktop/latest?version=${version}&env=${getReleaseChannel()}`;
}
function shouldMakeDelta() {
// Only production and beta channels include deltas. Test releases aren't
// necessarily sequential so deltas wouldn't make sense.
const channelsWithDeltas = ['production', 'beta'];
return channelsWithDeltas.indexOf(getReleaseChannel()) > -1;
}
exports.getDistRoot = getDistRoot;
exports.getDistPath = getDistPath;
exports.getExecutableName = getExecutableName;
exports.getOSXZipName = getOSXZipName;
exports.getOSXZipPath = getOSXZipPath;
exports.getWindowsInstallerName = getWindowsInstallerName;
exports.getWindowsStandaloneName = getWindowsStandaloneName;
exports.getWindowsStandalonePath = getWindowsStandalonePath;
exports.getWindowsFullNugetPackageName = getWindowsFullNugetPackageName;
exports.getWindowsFullNugetPackagePath = getWindowsFullNugetPackagePath;
exports.getWindowsDeltaNugetPackageName = getWindowsDeltaNugetPackageName;
exports.getWindowsDeltaNugetPackagePath = getWindowsDeltaNugetPackagePath;
exports.getWindowsIdentifierName = getWindowsIdentifierName;
exports.getBundleSizes = getBundleSizes;
exports.getReleaseBranchName = getReleaseBranchName;
exports.getReleaseChannel = getReleaseChannel;
exports.getReleaseSHA = getReleaseSHA;
exports.getUpdatesURL = getUpdatesURL;
exports.shouldMakeDelta = shouldMakeDelta;

26
config/package-info.js Normal file
View File

@ -0,0 +1,26 @@
const appPackage = require('../package.json')
function getProductName() {
const productName = appPackage.productName;
return process.env.NODE_ENV === 'development'
? `${productName}-dev`
: productName;
}
function getCompanyName() {
return appPackage.companyName;
}
function getVersion() {
return appPackage.version;
}
function getBundleID() {
return appPackage.bundleID;
}
exports.getProductName = getProductName;
exports.getCompanyName = getCompanyName;
exports.getVersion = getVersion;
exports.getBundleID = getBundleID;

View File

@ -4,12 +4,15 @@ const ElectronConnectWebpackPlugin = require('electron-connect-webpack-plugin');
const nodeExternals = require('webpack-node-externals'); const nodeExternals = require('webpack-node-externals');
const { hasProcessFlag, root } = require('./helpers.js'); const { hasProcessFlag, root } = require('./helpers.js');
const { getReplacements } = require('./app-info');
const replacements = getReplacements();
const EVENT = process.env.npm_lifecycle_event || ''; const EVENT = process.env.npm_lifecycle_event || '';
const PROD = EVENT.includes('prod'); const PROD = EVENT.includes('prod');
module.exports = function() { module.exports = function () {
const tsConfigBase = './src/tsconfig.electron.json'; const tsConfigBase = './src/tsconfig.electron.json';
const atlConfig = { const atlConfig = {
@ -47,6 +50,11 @@ module.exports = function() {
}; };
config.plugins = [ config.plugins = [
PROD ? new webpack.NoEmitOnErrorsPlugin() : null, PROD ? new webpack.NoEmitOnErrorsPlugin() : null,
new webpack.DefinePlugin(
Object.assign({}, replacements, {
__PROCESS_KIND__: JSON.stringify('main'),
})
),
new ElectronConnectWebpackPlugin({ new ElectronConnectWebpackPlugin({
path: root('build', 'dev'), path: root('build', 'dev'),
stopOnClose: true, stopOnClose: true,

View File

@ -1,13 +1,29 @@
var fse = require('fs-extra'); var fse = require('fs-extra');
const util = require('util'); const util = require('util');
module.exports = function(config) { const webpack = require('webpack');
const { getReplacements } = require('./app-info');
const replacements = getReplacements();
module.exports = function (config) {
config.target = 'electron-renderer'; config.target = 'electron-renderer';
config.plugins.push(
new webpack.DefinePlugin(
Object.assign({}, replacements, {
__PROCESS_KIND__: JSON.stringify('renderer'),
})
)
);
// console.log(config.module); // console.log(config.module);
// console.log(util.inspect(config.module, {showHidden: false, depth: null})); // console.log(util.inspect(config.module, {showHidden: false, depth: null}));
// var json = util.inspect(config, {showHidden: false, depth: null}); var json = util.inspect(config, { showHidden: false, depth: null });
// fse.writeFileSync('webpack.js', json, 'utf8'); fse.writeFileSync('webpack.js', json, 'utf8');
return config; return config;
}; };

View File

@ -48,7 +48,7 @@
// maximize/restore and close. On macOS the controls are added // maximize/restore and close. On macOS the controls are added
// automatically even for borderless window so we only render // automatically even for borderless window so we only render
// controls on Windows. // controls on Windows.
.window-controls { of-window-controls {
flex-grow: 0; flex-grow: 0;
flex-shrink: 0; flex-shrink: 0;
margin-left: auto; margin-left: auto;

View File

@ -0,0 +1,282 @@
// import { BrowserWindow, ipcMain, Menu, app, dialog } from 'electron';
// import { encodePathAsUrl } from '../lib/path';
// import { registerWindowStateChangedEvents } from '../lib/window-state';
// import { MenuEvent } from './menu';
// import { URLActionType } from '../lib/parse-app-url';
// import { ILaunchStats } from '../lib/stats';
// import { menuFromElectronMenu } from '../models/app-menu';
// import { now } from './now';
// import * as path from 'path';
// let windowStateKeeper: any | null = null;
// export class AppWindow {
// private window: Electron.BrowserWindow;
// private emitter = new Emitter();
// private _loadTime: number | null = null;
// private _rendererReadyTime: number | null = null;
// private minWidth = 960;
// private minHeight = 660;
// public constructor() {
// if (!windowStateKeeper) {
// // `electron-window-state` requires Electron's `screen` module, which can
// // only be required after the app has emitted `ready`. So require it
// // lazily.
// windowStateKeeper = require('electron-window-state');
// }
// const savedWindowState = windowStateKeeper({
// defaultWidth: this.minWidth,
// defaultHeight: this.minHeight,
// });
// const windowOptions: Electron.BrowserWindowConstructorOptions = {
// x: savedWindowState.x,
// y: savedWindowState.y,
// width: savedWindowState.width,
// height: savedWindowState.height,
// minWidth: this.minWidth,
// minHeight: this.minHeight,
// show: false,
// // This fixes subpixel aliasing on Windows
// // See https://github.com/atom/atom/commit/683bef5b9d133cb194b476938c77cc07fd05b972
// backgroundColor: '#fff',
// webPreferences: {
// // Disable auxclick event
// // See https://developers.google.com/web/updates/2016/10/auxclick
// disableBlinkFeatures: 'Auxclick',
// // Enable, among other things, the ResizeObserver
// experimentalFeatures: true,
// },
// acceptFirstMouse: true,
// };
// if (__DARWIN__) {
// windowOptions.titleBarStyle = 'hidden';
// } else if (__WIN32__) {
// windowOptions.frame = false;
// } else if (__LINUX__) {
// windowOptions.icon = path.join(__dirname, 'static', 'icon-logo.png');
// }
// this.window = new BrowserWindow(windowOptions);
// savedWindowState.manage(this.window);
// let quitting = false;
// app.on('before-quit', () => {
// quitting = true;
// });
// ipcMain.on('will-quit', (event: Electron.IpcMessageEvent) => {
// quitting = true;
// event.returnValue = true;
// });
// // on macOS, when the user closes the window we really just hide it. This
// // lets us activate quickly and keep all our interesting logic in the
// // renderer.
// if (__DARWIN__) {
// this.window.on('close', e => {
// if (!quitting) {
// e.preventDefault();
// Menu.sendActionToFirstResponder('hide:');
// }
// });
// }
// }
// public load() {
// let startLoad = 0;
// // We only listen for the first of the loading events to avoid a bug in
// // Electron/Chromium where they can sometimes fire more than once. See
// // See
// // https://github.com/desktop/desktop/pull/513#issuecomment-253028277. This
// // shouldn't really matter as in production builds loading _should_ only
// // happen once.
// this.window.webContents.once('did-start-loading', () => {
// this._rendererReadyTime = null;
// this._loadTime = null;
// startLoad = now();
// });
// this.window.webContents.once('did-finish-load', () => {
// if (process.env.NODE_ENV === 'development') {
// this.window.webContents.openDevTools();
// }
// this._loadTime = now() - startLoad;
// this.maybeEmitDidLoad();
// });
// this.window.webContents.on('did-finish-load', () => {
// this.window.webContents.setVisualZoomLevelLimits(1, 1);
// });
// this.window.webContents.on('did-fail-load', () => {
// this.window.webContents.openDevTools();
// this.window.show();
// });
// // TODO: This should be scoped by the window.
// ipcMain.once(
// 'renderer-ready',
// (event: Electron.IpcMessageEvent, readyTime: number) => {
// this._rendererReadyTime = readyTime;
// this.maybeEmitDidLoad();
// }
// );
// this.window.on('focus', () => this.window.webContents.send('focus'));
// this.window.on('blur', () => this.window.webContents.send('blur'));
// registerWindowStateChangedEvents(this.window);
// this.window.loadURL(encodePathAsUrl(__dirname, 'index.html'));
// }
// /**
// * Emit the `onDidLoad` event if the page has loaded and the renderer has
// * signalled that it's ready.
// */
// private maybeEmitDidLoad() {
// if (!this.rendererLoaded) {
// return;
// }
// this.emitter.emit('did-load', null);
// }
// /** Is the page loaded and has the renderer signalled it's ready? */
// private get rendererLoaded(): boolean {
// return !!this.loadTime && !!this.rendererReadyTime;
// }
// public onClose(fn: () => void) {
// this.window.on('closed', fn);
// }
// /**
// * Register a function to call when the window is done loading. At that point
// * the page has loaded and the renderer has signalled that it is ready.
// */
// public onDidLoad(fn: () => void): Disposable {
// return this.emitter.on('did-load', fn);
// }
// public isMinimized() {
// return this.window.isMinimized();
// }
// /** Is the window currently visible? */
// public isVisible() {
// return this.window.isVisible();
// }
// public restore() {
// this.window.restore();
// }
// public focus() {
// this.window.focus();
// }
// /** Show the window. */
// public show() {
// this.window.show();
// }
// /** Send the menu event to the renderer. */
// public sendMenuEvent(name: MenuEvent) {
// this.show();
// this.window.webContents.send('menu-event', { name });
// }
// /** Send the URL action to the renderer. */
// public sendURLAction(action: URLActionType) {
// this.show();
// this.window.webContents.send('url-action', { action });
// }
// /** Send the app launch timing stats to the renderer. */
// public sendLaunchTimingStats(stats: ILaunchStats) {
// this.window.webContents.send('launch-timing-stats', { stats });
// }
// /** Send the app menu to the renderer. */
// public sendAppMenu() {
// const appMenu = Menu.getApplicationMenu();
// if (appMenu) {
// const menu = menuFromElectronMenu(appMenu);
// this.window.webContents.send('app-menu', { menu });
// }
// }
// /** Send a certificate error to the renderer. */
// public sendCertificateError(
// certificate: Electron.Certificate,
// error: string,
// url: string
// ) {
// this.window.webContents.send('certificate-error', {
// certificate,
// error,
// url,
// });
// }
// public showCertificateTrustDialog(
// certificate: Electron.Certificate,
// message: string
// ) {
// // The Electron type definitions don't include `showCertificateTrustDialog`
// // yet.
// const d = dialog as any;
// d.showCertificateTrustDialog(
// this.window,
// { certificate, message },
// () => { }
// );
// }
// /** Report the exception to the renderer. */
// public sendException(error: Error) {
// // `Error` can't be JSONified so it doesn't transport nicely over IPC. So
// // we'll just manually copy the properties we care about.
// const friendlyError = {
// stack: error.stack,
// message: error.message,
// name: error.name,
// };
// this.window.webContents.send('main-process-exception', friendlyError);
// }
// /**
// * Get the time (in milliseconds) spent loading the page.
// *
// * This will be `null` until `onDidLoad` is called.
// */
// public get loadTime(): number | null {
// return this._loadTime;
// }
// /**
// * Get the time (in milliseconds) elapsed from the renderer being loaded to it
// * signaling it was ready.
// *
// * This will be `null` until `onDidLoad` is called.
// */
// public get rendererReadyTime(): number | null {
// return this._rendererReadyTime;
// }
// public destroy() {
// this.window.destroy();
// }
// }