1
0
mirror of https://github.com/mgerb/ps-launcher synced 2026-01-08 09:32:51 +00:00

lots of progress - basic functionality work - bundling working

This commit is contained in:
2017-10-18 23:37:05 -05:00
parent c4e028d102
commit 96f9c4ca5d
27 changed files with 1178 additions and 174 deletions

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.vscode
node_modules
yarn-error*
dist
dist
build

View File

@@ -1 +1 @@
declare module 'this-is-a-test-module';
declare module '*.png';

View File

@@ -1,5 +1,7 @@
# Work in progress
## Early Prototype 10/17/2017
## Prototype 2 10/18/2017
![alt text](https://github.com/mgerb/ps-launcher/blob/master/docs/images/prototype2.PNG?raw=true "Prototype 2")
![alt text](https://github.com/mgerb/ps-launcher/blob/master/docs/images/prototype1.PNG?raw=true "Logo Title Text 1")
## Early Prototype 10/17/2017
![alt text](https://github.com/mgerb/ps-launcher/blob/master/docs/images/prototype1.PNG?raw=true "Prototype 1")

View File

@@ -1,3 +1,5 @@
@import '../scss/variables.scss';
.wrapper {
display: flex;
flex-direction: column;

View File

@@ -1,26 +1,33 @@
import React from 'react';
import { Provider } from 'mobx-react';
import AppState from '../state/AppState';
import { inject, observer } from 'mobx-react';
import { AppState } from '../state/AppState';
import { Content, Header, ServerList, SubHeader } from '../components';
import './Wrapper.scss';
const stores = { AppState };
interface Props {
AppState?: AppState;
}
export class Wrapper extends React.Component<any, any> {
@inject('AppState')
@observer
export class Wrapper extends React.Component<Props, any> {
public render(): any {
private renderMain(): any {
return (
<Provider {...stores}>
<div className="wrapper">
<Header />
<SubHeader />
<div style={{ display: 'flex', flex: 1 }}>
<ServerList />
<Content />
</div>
<div className="wrapper">
<Header />
<SubHeader />
<div style={{ display: 'flex', flex: 1 }}>
<ServerList />
<Content />
</div>
</Provider>
</div>
);
}
public render(): any {
// make sure app is bootstrapped before rendering
return this.props.AppState.isBootstrapped ? this.renderMain() : <div />;
}
}

View File

@@ -1,9 +1,23 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'mobx-react';
import AppState from './state/AppState';
import { Wrapper } from './Wrapper/Wrapper';
import 'babel-polyfill';
// base styling
import './scss/index.scss';
ReactDOM.render(<Wrapper />, document.getElementById('app'));
const stores = { AppState };
class App extends React.Component<any, any> {
public render(): any {
return (
<Provider {...stores}>
<Wrapper />
</Provider>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));

BIN
app/assets/bc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

BIN
app/assets/vanilla.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

BIN
app/assets/wotlk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

View File

@@ -13,7 +13,7 @@
align-items: center;
justify-content: center;
background: $blue;
transition: background 0.2s;
transition: background 0.2s linear;
outline: none;
padding: 0 15px;

View File

@@ -1,6 +1,4 @@
import React from 'react';
// import fs from 'fs';
// import { exec } from 'child_process';
import { inject, observer } from 'mobx-react';
import * as _ from 'lodash';
import { AppState } from '../../state/AppState';
@@ -21,29 +19,6 @@ export class Content extends React.Component<Props, any> {
};
}
// private async startGame(): Promise<void> {
// const { path } = this.state;
// // set the realm list
// await this.setRealmList();
// // launch wow
// exec(`"${path}/WoW.exe"`, (err, output) => {
// console.log(err);
// console.log(output);
// });
// }
// private setRealmList(): Promise<any> {
// const { path } = this.state;
// return new Promise((resolve, reject) => {
// fs.writeFile(`${path}/realmlist.wtf`, 'set realmlist logon.elysium-project.org', err => {
// err ? reject(err) : resolve();
// });
// });
// }
private onFolderSelect(e: React.ChangeEvent<HTMLInputElement>): void {
const path: string = _.get(e, `target.files[0].path`);

View File

@@ -42,3 +42,8 @@
width: 10px;
border: 2px solid;
}
.header__version {
color: $blue--lighter;
font-size: 12px;
}

View File

@@ -8,6 +8,8 @@ interface Props {
AppState?: AppState;
}
declare const VERSION: any;
@inject('AppState')
@observer
export class Header extends React.Component<Props, any> {
@@ -35,6 +37,7 @@ export class Header extends React.Component<Props, any> {
public render(): any {
return (
<div className="header">
<div className="header__version">PS Launcher <span style={{ fontSize: '10px' }}>v{VERSION}</span></div>
<div className="header__draggable-region"></div>
<div className="header-icon header-icon--minimize" onClick={this.minimize.bind(this)}/>
<div className="header-icon header-icon--maximize" onClick={this.maximize.bind(this)}/>

View File

@@ -1,7 +1,73 @@
@import '../../scss/variables.scss';
.server-list {
display: flex;
flex-direction: column;
background: darken($dark-blue, 1%);
width: 200px;
min-width: 200px;
border-right: 1px solid $dark-blue--3;
.server-list-heading {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
.fa {
cursor: pointer;
color: $blue;
&:hover {
color: $blue--lighter;
}
}
}
.item-container {
overflow-y: auto;
flex: 1;
}
.list-item {
cursor: pointer;
height: 50px;
line-height: 50px;
text-align: center;
background: darken($dark-blue, 1%);
transition: all 0.2s linear;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
&:hover, &.selected {
background: $dark-blue--1;
color: lighten($white, 10%);
border-left-color: $blue;
}
}
.start-button-container {
height: 100px;
padding: 10px;
}
.start-button {
height: 100%;
width: 100%;
border: none;
background: $blue;
border-radius: 4px;
cursor: pointer;
color: $white;
font-size: 30px;
&:disabled {
opacity: 0.5;
pointer-events: none;
}
&:hover {
background: $blue--lighter;
}
}
}

View File

@@ -1,4 +1,6 @@
import React from 'react';
import fs from 'fs';
import { exec } from 'child_process';
import { inject, observer } from 'mobx-react';
import { AppState } from '../../state/AppState';
@@ -11,14 +13,62 @@ interface Props {
@inject('AppState')
@observer
export class ServerList extends React.Component<Props, any> {
constructor(props: Props) {
super(props);
}
private renderItems(): any {
const { AppState } = this.props;
return this.props.AppState.selectedExpansion.servers.map((server, index) => {
const selected = AppState.selectedExpansion.selectedServerIndex === index ? ' selected' : '';
return (
<div key={index} className={'list-item' + selected} onClick={() => AppState.setSelectedServerIndex(index)}>
{server.name}
</div>
);
});
}
private async play(): Promise<void> {
const { directory } = this.props.AppState.selectedExpansion;
// set the realm list
await this.setRealmList();
// launch wow
exec(`"${directory}/WoW.exe"`, (err, output) => {
console.log(err);
console.log(output);
});
}
private setRealmList(): Promise<any> {
const { directory } = this.props.AppState.selectedExpansion;
const { realmlist } = this.props.AppState.selectedServer;
return new Promise((resolve, reject) => {
fs.writeFile(`${directory}/realmlist.wtf`, `set realmlist ${realmlist}`, err => {
err ? reject(err) : resolve();
});
});
}
public render(): any {
const { selectedServer } = this.props.AppState;
return (
<div className="server-list">Server list</div>
<div className="server-list">
<div className="server-list-heading">
<div>Servers</div>
<i className="fa fa-plus"/>
</div>
<div className="item-container">{this.renderItems()}</div>
<div className="start-button-container">
<button className="start-button" onClick={() => this.play()} disabled={!selectedServer}>
Play
</button>
</div>
</div>
);
}
}

View File

@@ -5,10 +5,11 @@
border-bottom: 1px solid $dark-blue--3;
display: flex;
height: 100px;
overflow: auto;
overflow-x: auto;
.sub-header__item {
width: 100px;
min-width: 180px;
width: 180px;
display: flex;
align-items: center;
justify-content: center;
@@ -17,6 +18,10 @@
&:hover, &.selected {
background: $dark-blue--3;
}
}
.exp-image {
width: 90%;
height: auto;
}
}

View File

@@ -3,6 +3,16 @@ import { inject, observer } from 'mobx-react';
import * as _ from 'lodash';
import { AppState } from '../../state/AppState';
import vanilla from '../../assets/vanilla.png';
import bc from '../../assets/bc.png';
import wotlk from '../../assets/wotlk.png';
const images: any = {
vanilla,
bc,
wotlk,
};
import './SubHeader.scss';
interface Props {
@@ -26,10 +36,10 @@ export class SubHeader extends React.Component<Props, any> {
private renderItems(): any {
const { selectedExpKey } = this.props.AppState;
return _.map(this.props.AppState.expansions, (exp, key) => {
return _.map(this.props.AppState.expansions, (_, key) => {
return (
<div key={key} className={'sub-header__item ' + (key === selectedExpKey && 'selected')} onClick={() => this.selectExpansion(key)}>
<div style={{ textAlign: 'center' }}>{exp.name}</div>
<img src={images[key]} className="exp-image"/>
</div>
);
});

View File

@@ -21,10 +21,30 @@ body {
a {
text-decoration: none;
color: $blue;
transition: all 0.1s linear;
transition: all 0.2s linear;
cursor: pointer;
&:hover {
color: $blue--lighter;
}
}
::-webkit-scrollbar-track
{
border-radius: 10px;
background-color: none;
}
::-webkit-scrollbar
{
width: 5px;
height: 5px;
background-color: none;
border-radius: 10px;
}
::-webkit-scrollbar-thumb
{
border-radius: 10px;
background-color: darken($dark-blue, 5%);
}

View File

@@ -1,10 +1,14 @@
import { remote } from 'electron';
import * as _ from 'lodash';
import { action, computed, observable, runInAction } from 'mobx';
import fs from 'fs';
import { persistentStateSeed } from './persistent-state-seed';
export interface ExpansionType {
name: string;
servers: ServerType[];
directory?: string;
selectedServerIndex?: number;
}
export interface ServerType {
@@ -14,47 +18,39 @@ export interface ServerType {
}
export class AppState {
private appPath: string;
private persistentFilePath: string;
constructor() {
this.appPath = remote.app.getPath('appData') + '/' + remote.app.getName();
this.persistentFilePath = this.appPath + '/state.json';
this.bootstrap();
}
@action
private bootstrap(): void {
fs.readFile('./servers.json', (err, data) => {
if (!err) {
runInAction(() => {
this.expansions = JSON.parse(data.toString()) as any;
});
}
});
}
@observable
public expansions: { [key: string]: ExpansionType } = {
vanilla: {
name: 'Vanilla',
servers: [],
directory: '',
},
bc: {
name: 'Burning Crusade',
servers: [],
directory: '',
},
wotlk: {
name: 'Wrath of the Lich King',
servers: [],
directory: '',
},
};
@observable
public selectedExpKey: string = 'vanilla';
@observable public expansions: { [key: string]: ExpansionType };
@observable public isBootstrapped: boolean = false;
@observable public selectedExpKey: string = 'vanilla';
@computed
public get selectedExpansion(): ExpansionType {
return this.expansions[this.selectedExpKey];
return _.get(this.expansions, `[${this.selectedExpKey}]`);
}
@computed
public get selectedServer(): ServerType {
if (this.selectedExpansion.servers.length < 1) {
return null;
}
const index: number = this.selectedExpansion.selectedServerIndex;
return this.selectedExpansion.servers[index];
}
@action
public setSelectedServerIndex(index: number): void {
this.selectedExpansion.selectedServerIndex = index;
this.updateFile(this.expansions);
}
@action
@@ -64,18 +60,47 @@ export class AppState {
@action
public setDirectory(dir: string): void {
this.expansions[this.selectedExpKey].directory = dir;
this.selectedExpansion.directory = dir;
this.updateFile(this.expansions);
}
// bootstrap application
// creates directory and state.json in appData
@action
private bootstrap(): void {
if (!fs.statSync(this.appPath).isDirectory()) {
fs.mkdirSync(this.appPath);
}
fs.stat(this.persistentFilePath, err => {
// create file if not exists
if (err) {
runInAction(() => {
this.expansions = persistentStateSeed();
this.isBootstrapped = true;
});
this.updateFile(this.expansions);
} else {
fs.readFile(this.persistentFilePath, (err, data) => {
if (!err) {
runInAction(() => {
this.expansions = JSON.parse(data.toString()) as any;
this.isBootstrapped = true;
});
}
});
}
});
}
private updateFile(exp: any): Promise<void> {
return new Promise((resolve, reject) => {
fs.writeFile('./servers.json', JSON.stringify(exp, null, 2), {}, (err) => {
fs.writeFile(this.persistentFilePath, JSON.stringify(exp, null, 2), {}, err => {
err ? reject(err) : resolve();
});
});
}
}
export default new AppState();

View File

@@ -0,0 +1,39 @@
// object is used to create state.json inside user data directory
export const persistentStateSeed = (): any => {
return {
vanilla: {
name: 'Vanilla',
servers: [
{
name: 'Lights Hope',
realmlist: 'logon.lightshope.org',
website: 'www.lightshope.org',
},
{
name: 'Elysium',
realmlist: '',
website: 'www.elysium-project.org',
},
{
name: 'Kronos',
realmlist: 'login.kronos-wow.com',
website: 'www.kronos-wow.com',
},
],
directory: '',
selectedServerIndex: 0,
},
bc: {
name: 'Burning Crusade',
servers: [] as any,
directory: '',
selectedServerIndex: 0,
},
wotlk: {
name: 'Wrath of the Lich King',
servers: [] as any,
directory: '',
selectedServerIndex: 0,
},
};
};

BIN
docs/images/prototype2.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

View File

@@ -4,7 +4,7 @@
</head>
<body>
<div id="app" style="height: 100%"></div>
<div id="app" style="height: 100%; overflow: hidden"></div>
</body>
</html>

16
main.js
View File

@@ -16,15 +16,15 @@ function createWindow() {
mainWindow = new BrowserWindow({ width: 800, height: 600, frame: false });
// and load the index.html of the app.
// mainWindow.loadURL(
// url.format({
// pathname: path.join(__dirname, './dist/index.html'),
// protocol: 'file:',
// slashes: true,
// }),
// );
mainWindow.loadURL(
url.format({
pathname: path.join(__dirname, './dist/index.html'),
protocol: 'file:',
slashes: true,
}),
);
mainWindow.loadURL('http://localhost:8080');
// mainWindow.loadURL('http://localhost:8080');
// Open the DevTools.
// mainWindow.webContents.openDevTools()

View File

@@ -1,6 +1,6 @@
{
"name": "todoapp",
"version": "1.0.0",
"name": "ps-launcher",
"version": "0.0.1",
"description": "A seed for a simple react application with typescript.",
"main": "main.js",
"scripts": {
@@ -8,8 +8,25 @@
"c9": "webpack-dev-server --host 0.0.0.0 --port 8080 --inline --history-api-fallback",
"dev": "webpack --watch --colors",
"dev:server": "webpack-dev-server --inline --history-api-fallback",
"start": "electron ./main.js",
"watch": "webpack --watch"
"start": "electron .",
"watch": "webpack --watch",
"pack": "electron-builder --dir",
"dist": "electron-builder"
},
"build": {
"appId": "ps-launcher",
"directories": {
"output": "build"
},
"win": {
"target": "nsis"
},
"nsis": {
"oneClick": false,
"perMachine": true,
"runAfterFinish": true,
"deleteAppDataOnUninstall": true
}
},
"author": "Mitchell Gerber",
"license": "MIT",
@@ -29,7 +46,7 @@
"babel-preset-stage-0": "^6.16.0",
"clean-webpack-plugin": "^0.1.14",
"css-loader": "^0.28.7",
"electron": "^1.7.9",
"electron-builder-squirrel-windows": "^19.39.0",
"extract-text-webpack-plugin": "3.0.1",
"file-loader": "^1.1.5",
"font-awesome": "^4.7.0",
@@ -51,5 +68,9 @@
"url-loader": "^0.6.2",
"webpack": "3.7.1",
"webpack-dev-server": "2.9.2"
},
"devDependencies": {
"electron": "^1.7.9",
"electron-builder": "^19.37.2"
}
}

View File

@@ -1,28 +0,0 @@
{
"vanilla": {
"name": "Vanilla",
"servers": [
{
"name": "Elysium",
"realmlist": "",
"website": ""
},
{
"name": "Kronos",
"realmlist": "",
"website": ""
}
],
"directory": ""
},
"bc": {
"name": "Burning Crusade",
"servers": [],
"directory": ""
},
"wotlk": {
"name": "Wrath of the Lich King",
"servers": [],
"directory": ""
}
}

View File

@@ -43,28 +43,31 @@ module.exports = {
},
{
test: /\.svg$/,
use:
'url-loader?limit=65000&mimetype=image/svg+xml&name=static/[name].[hash].[ext]',
use: 'url-loader?limit=65000&mimetype=image/svg+xml&name=static/[name].[hash].[ext]',
},
{
test: /\.woff$/,
use:
'url-loader?limit=65000&mimetype=application/font-woff&name=static/[name].[hash].[ext]',
use: 'url-loader?limit=65000&mimetype=application/font-woff&name=static/[name].[hash].[ext]',
},
{
test: /\.woff2$/,
use:
'url-loader?limit=65000&mimetype=application/font-woff2&name=static/[name].[hash].[ext]',
use: 'url-loader?limit=65000&mimetype=application/font-woff2&name=static/[name].[hash].[ext]',
},
{
test: /\.[ot]tf$/,
use:
'url-loader?limit=65000&mimetype=application/octet-stream&name=static/[name].[hash].[ext]',
use: 'url-loader?limit=65000&mimetype=application/octet-stream&name=static/[name].[hash].[ext]',
},
{
test: /\.eot$/,
use:
'url-loader?limit=65000&mimetype=application/vnd.ms-fontobject&name=static/[name].[hash].[ext]',
use: 'url-loader?limit=65000&mimetype=application/vnd.ms-fontobject&name=static/[name].[hash].[ext]',
},
{
test: /\.(?:png|jpg|svg)$/,
loader: 'file-loader',
query: {
// Inline images smaller than 10kb as data URIs
limit: 10000,
},
},
],
},
@@ -86,5 +89,8 @@ module.exports = {
minChunks: 'Infinity',
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
VERSION: JSON.stringify(require("./package.json").version)
})
],
};

847
yarn.lock

File diff suppressed because it is too large Load Diff