diff --git a/app/app.tsx b/app/app.tsx index bf15890..bbfb827 100644 --- a/app/app.tsx +++ b/app/app.tsx @@ -1,6 +1,16 @@ +import { Provider } from 'mobx-react'; import React from 'react'; import ReactDOM from 'react-dom'; -import { Home } from './components'; import './scss/index.scss'; +import { RootStore } from './stores/root.store'; +import { Wrapper } from './wrapper'; -ReactDOM.render(, document.getElementById('app')); +const rootStore = new RootStore(); + +const app = ( + + + +); + +ReactDOM.render(app, document.getElementById('app')); diff --git a/app/components/home.tsx b/app/components/home.tsx deleted file mode 100644 index e3e43ef..0000000 --- a/app/components/home.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { WebviewTag } from 'electron'; -import * as _ from 'lodash'; -import React from 'react'; -import { ItemTextService } from '../services'; -import { PoeService } from '../services/poe.service'; -import { LoginWebview } from './login-webview'; -import { PriceListItem } from './price-list-item/price-list-item'; - -interface IState { - showLogin: boolean; - data: any; -} - -export class Home extends React.Component { - private webview: WebviewTag; - - constructor(props: any) { - super(props); - this.state = { - showLogin: false, - data: [], - }; - } - - componentDidMount() { - this.addWebviewListeners(); - } - - public addWebviewListeners() { - this.webview = this.refs['webview'] as any; - this.webview.addEventListener('dom-ready', () => { - // this.webview.openDevTools(); - }); - - this.webview.addEventListener('ipc-message', e => { - if (e.channel === 'html-content') { - const htmlContents = e.args[0]; - this.handleResponse(htmlContents); - } - }); - } - - public handleResponse(res: any) { - const json = JSON.parse(res); - - if (json === null || json['error']) { - this.setState({ showLogin: true }); - } else if (json.items) { - const items = _.map(json.items, item => { - item.fullText = ItemTextService.parseItem(item); - return item; - }); - - this.setState({ showLogin: false, data: items }); - this.getItemPrice(items, 0); - } - } - - onLogin = () => { - this.setState({ showLogin: false }); - this.webview.reload(); - }; - - async getItemPrice(data: any[], index: number) { - if (!data[index] || data !== this.state.data) { - return; - } - - console.log(data[index]); - console.log(data[index].fullText + '\n\n\n'); - const res = await PoeService.priceCheck(data[index].fullText); - if ((res.min && res.currency) || (res.currency_rec && res.min_price)) { - const dataCopy: any = [...data]; - dataCopy[index].priceInfo = res; - await new Promise(resolve => this.setState({ data: dataCopy }, resolve)); - } - - setTimeout(() => { - this.getItemPrice(this.state.data, index + 1); - }, 1000); - } - - renderPriceList = (data: any[]) => { - return data.map((item, index) => ); - }; - - public render() { - const { data, showLogin } = this.state; - - const url = - 'https://www.pathofexile.com/character-window/get-stash-items' + - '?league=Incursion&tabs=1&tabIndex=0&accountName=DoctorCoctor'; - - return ( -
- {showLogin && } - {data && this.renderPriceList(data)} - {/* {data && } */} - -
- ); - } -} diff --git a/app/components/home/home.scss b/app/components/home/home.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/components/home/home.tsx b/app/components/home/home.tsx new file mode 100644 index 0000000..48929f1 --- /dev/null +++ b/app/components/home/home.tsx @@ -0,0 +1,34 @@ +import { inject, observer } from 'mobx-react'; +import React from 'react'; +import { AppStore } from '../../stores/app.store'; +import { Navbar } from '../navbar/navbar'; +import { PriceListItem } from '../price-list-item/price-list-item'; +import './home.scss'; + +interface IProps { + appStore?: AppStore; +} + +interface IState {} + +@inject('appStore') +@observer +export class Home extends React.Component { + constructor(props: IProps) { + super(props); + this.state = {}; + } + + render() { + return ( +
+ +
+ {this.props.appStore!.stashItems.map((v, k) => { + return ; + })} +
+
+ ); + } +} diff --git a/app/components/index.ts b/app/components/index.ts index 0e509a8..5c708b8 100644 --- a/app/components/index.ts +++ b/app/components/index.ts @@ -1,5 +1,6 @@ -export * from './home'; -export * from './login-webview'; -export * from './price-list'; +export * from './home/home'; +export * from './login-webview/login-webview'; +export * from './navbar/navbar'; export * from './price-list-item/price-list-item'; -export * from './stash-tab'; +export * from './price-list/price-list'; +export * from './stash-tab/stash-tab'; diff --git a/app/components/login-webview.tsx b/app/components/login-webview.tsx deleted file mode 100644 index 01634ed..0000000 --- a/app/components/login-webview.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { WebviewTag } from 'electron'; -import React from 'react'; - -interface IProps { - onSuccess: () => void; -} - -export class LoginWebview extends React.Component { - private webview: WebviewTag; - - constructor(props: IProps) { - super(props); - } - - componentDidMount() { - this.webview = this.refs['webview'] as any; - - this.webview.addEventListener('did-stop-loading', this.didStopLoading); - } - - didStopLoading = (event: any) => { - if ((event.target.src as string).includes('my-account')) { - this.props.onSuccess(); - } - }; - - render() { - return ( - - ); - } -} diff --git a/app/components/login-webview/login-webview.tsx b/app/components/login-webview/login-webview.tsx new file mode 100644 index 0000000..86bfe63 --- /dev/null +++ b/app/components/login-webview/login-webview.tsx @@ -0,0 +1,53 @@ +import { WebviewTag } from 'electron'; +import * as _ from 'lodash'; +import { inject, observer } from 'mobx-react'; +import React from 'react'; +import { POE_HOME } from '../../constants'; +import { AppStore } from '../../stores/app.store'; + +interface IProps { + appStore?: AppStore; +} + +@inject('appStore') +@observer +export class LoginWebview extends React.Component { + private webview: WebviewTag; + + constructor(props: IProps) { + super(props); + } + + componentDidMount() { + this.webview = this.refs['webview'] as any; + this.webview.addEventListener('did-stop-loading', this.didStopLoading); + } + + // if webview redirect to my-account - means user logged in + didStopLoading = (event: any) => { + if ((event.target.src as string).includes('my-account')) { + this.getCookies(); + } + }; + + // get cookies from login and store username/sessionID + getCookies() { + const session = this.webview.getWebContents().session; + session.cookies.get({ url: POE_HOME }, async (error, cookies) => { + if (error) { + console.error(error); + return; + } + try { + const cookie: any = _.find(cookies, c => c.name === 'POESESSID'); + this.props.appStore!.performLogin(cookie.value); + } catch (e) { + this.props.appStore!.resetState(); + } + }); + } + + render() { + return ; + } +} diff --git a/app/components/navbar/navbar.scss b/app/components/navbar/navbar.scss new file mode 100644 index 0000000..e371758 --- /dev/null +++ b/app/components/navbar/navbar.scss @@ -0,0 +1,10 @@ +@import '../../scss/variables.scss'; + +.navbar { + background: $dark2; + border-bottom: 1px solid $dark1; + height: 50px; + display: flex; + align-items: center; + padding: 0 20px; +} diff --git a/app/components/navbar/navbar.tsx b/app/components/navbar/navbar.tsx new file mode 100644 index 0000000..20fa2e3 --- /dev/null +++ b/app/components/navbar/navbar.tsx @@ -0,0 +1,68 @@ +import { inject, observer } from 'mobx-react'; +import React from 'react'; +import { AppStore } from '../../stores/app.store'; +import './navbar.scss'; + +interface IProps { + appStore?: AppStore; +} + +@inject('appStore') +@observer +export class Navbar extends React.Component { + constructor(props: IProps) { + super(props); + } + + onLeagueSelect = (event: any) => { + this.props.appStore!.setActiveLeague(event.target.value); + }; + + onStashSelect = (event: any) => { + this.props.appStore!.loadItems(event.target.value); + }; + + renderStashTabSelector() { + const { stashTabs } = this.props.appStore!; + if (stashTabs.length < 1) { + return null; + } + return ( + + ); + } + + renderLeagueSelector() { + const { activeLeague, leagues } = this.props.appStore!; + if (!leagues || !activeLeague) { + return null; + } + + return ( + + ); + } + + render() { + return ( +
+ {this.renderLeagueSelector()} + {this.renderStashTabSelector()} +
+ ); + } +} diff --git a/app/components/price-list-item/price-list-item.scss b/app/components/price-list-item/price-list-item.scss index 15a01e8..8994542 100644 --- a/app/components/price-list-item/price-list-item.scss +++ b/app/components/price-list-item/price-list-item.scss @@ -1,23 +1,22 @@ -@import '~open-color/open-color.scss'; +@import '../../scss/variables.scss'; .pli { - padding: 20px; + padding: 10px; border-radius: 4px; - background: $oc-gray-3; - border-color: $oc-gray-5; + background: $dark3; + border: 1px solid $dark1; display: flex; flex-direction: row; width: 300px; word-break: break-word; & + & { - margin-top: 20px; + margin-top: 10px; } &__img { - max-width: 100px; + max-width: 50px; max-height: 50px; - padding-right: 20px; - cursor: pointer; + padding-right: 10px; } } diff --git a/app/components/price-list-item/price-list-item.tsx b/app/components/price-list-item/price-list-item.tsx index bf23fe6..8e23543 100644 --- a/app/components/price-list-item/price-list-item.tsx +++ b/app/components/price-list-item/price-list-item.tsx @@ -1,8 +1,12 @@ import React from 'react'; +import { FrameType } from '../../model/frame-type'; +import { IItem } from '../../model/item'; +import { RarityColor } from '../../model/rarity-color'; +import { ItemTextService } from '../../services'; import './price-list-item.scss'; interface IProps { - data: any; + data: IItem; } interface IState { @@ -18,16 +22,23 @@ export class PriceListItem extends React.Component { componentDidMount() {} render() { - const { fullText, icon, priceInfo, typeLine } = this.props.data; + const { icon, priceInfo, stackSize, typeLine, frameType } = this.props.data; + const name = ItemTextService.filterName(typeLine); + const fullText = ItemTextService.parseItem(this.props.data); + + const rarityColor = RarityColor[(FrameType[frameType] || '').toLowerCase()]; return ( -
-
- +
console.log(this.props.data)}> +
+
-
+
- {typeLine} +
+ {name} +
+
{stackSize && Stack Size: {stackSize}}
{priceInfo ? `${priceInfo.min || priceInfo.min_price} ${priceInfo.currency || priceInfo.currency_rec}` : ''} diff --git a/app/components/price-list.tsx b/app/components/price-list.tsx deleted file mode 100644 index 3350150..0000000 --- a/app/components/price-list.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import { PriceListItem } from './price-list-item/price-list-item'; - -interface IProps { - data: any[]; -} - -export class PriceList extends React.Component { - constructor(props: IProps) { - super(props); - } - - // componentDidMount() { - // console.log(this.props.data); - // } - - // componentWillReceiveProps(nextProps: IProps) { - // if (this.props !== nextProps) { - // // do stuff - // console.log(nextProps); - // } - // } - - render = () => this.props.data.map((value, index) => ); -} diff --git a/app/components/price-list/price-list.tsx b/app/components/price-list/price-list.tsx new file mode 100644 index 0000000..af8346a --- /dev/null +++ b/app/components/price-list/price-list.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { PriceListItem } from '../price-list-item/price-list-item'; + +interface IProps { + data: any[]; +} + +export class PriceList extends React.Component { + constructor(props: IProps) { + super(props); + } + + render = () => this.props.data.map((value, index) => ); +} diff --git a/app/components/stash-tab.scss b/app/components/stash-tab/stash-tab.scss similarity index 100% rename from app/components/stash-tab.scss rename to app/components/stash-tab/stash-tab.scss diff --git a/app/components/stash-tab.tsx b/app/components/stash-tab/stash-tab.tsx similarity index 54% rename from app/components/stash-tab.tsx rename to app/components/stash-tab/stash-tab.tsx index 7e93fcd..961ba25 100644 --- a/app/components/stash-tab.tsx +++ b/app/components/stash-tab/stash-tab.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { PoeService } from '../services/poe.service'; import './stash-tab.scss'; interface IProps { @@ -7,13 +6,6 @@ interface IProps { } export class StashTab extends React.Component { - componentDidMount() { - console.log(this.props.data); - PoeService.priceCheck(`Superior Laboratory Map`).then(res => { - console.log(res); - }); - } - render() { return
; } diff --git a/app/constants.ts b/app/constants.ts new file mode 100644 index 0000000..f4f0fce --- /dev/null +++ b/app/constants.ts @@ -0,0 +1,7 @@ +export const POE_HOME = 'https://www.pathofexile.com'; +export const POE_LOGIN_URL = `${POE_HOME}/login`; +export const POE_LOGIN_STEAM_URL = `${POE_LOGIN_URL}/steam`; +export const POE_MY_ACCOUNT_URL = `${POE_HOME}/my-account`; +export const POE_GET_CHARACTERS_URL = `${POE_HOME}/character-window/get-characters`; +export const POE_STASH_ITEMS_URL = `${POE_HOME}/character-window/get-stash-items`; +export const POE_LEAGUE_LIST_URL = 'http://api.pathofexile.com/leagues?type=main&compact=1'; diff --git a/app/http/http.ts b/app/http/http.ts index 929c7fb..d7537c1 100644 --- a/app/http/http.ts +++ b/app/http/http.ts @@ -1,3 +1,7 @@ import axios from 'axios'; export const http = axios.create(); + +export const setHeader = (header: string, value: string) => { + http.defaults.headers[header] = value; +}; diff --git a/app/model/index.ts b/app/model/index.ts new file mode 100644 index 0000000..72fd670 --- /dev/null +++ b/app/model/index.ts @@ -0,0 +1 @@ +export * from './league'; diff --git a/app/model/item.ts b/app/model/item.ts new file mode 100644 index 0000000..5ea6ab2 --- /dev/null +++ b/app/model/item.ts @@ -0,0 +1,21 @@ +export interface IItem { + category: any[]; + descrText: string; + explicitMods: string[]; + frameType: number; + h: number; + icon: string; + id: string; + identified: boolean; + ilvl: number; + inventoryId: string; + league: string; + name: string; + properties: any[]; + stackSize?: number; + typeLine: string; + verified: boolean; + w: number; + x: number; + y: number; +} diff --git a/app/model/league.ts b/app/model/league.ts new file mode 100644 index 0000000..4a6b71e --- /dev/null +++ b/app/model/league.ts @@ -0,0 +1,6 @@ +export interface ILeague { + endAt?: string; + id: string; + startAt: string; + url: string; +} diff --git a/app/model/rarity-color.ts b/app/model/rarity-color.ts new file mode 100644 index 0000000..08fcad8 --- /dev/null +++ b/app/model/rarity-color.ts @@ -0,0 +1,6 @@ +export const RarityColor: any = { + unique: '#af6025', + rare: '#a3a314', + magic: 'rgb(117, 117, 255)', + normal: '#eee', +}; diff --git a/app/model/stash-tab.ts b/app/model/stash-tab.ts new file mode 100644 index 0000000..3df2d2c --- /dev/null +++ b/app/model/stash-tab.ts @@ -0,0 +1,18 @@ +interface IStashTabColor { + r: number; + g: number; + b: number; +} + +export interface IStashTab { + colour: IStashTabColor; + hidden: boolean; + i: number; + id: string; + n: string; + selected: boolean; + srcC: string; + srcL: string; + srcR: string; + type: string; +} diff --git a/app/scss/style.scss b/app/scss/style.scss index 32550a0..a52d7fa 100644 --- a/app/scss/style.scss +++ b/app/scss/style.scss @@ -1,3 +1,27 @@ +@import './variables.scss'; + html { font-family: 'Roboto Condensed', sans-serif; } + +html, +body, +#app { + height: 100%; + background: $dark4; + color: $textPrimary; +} + +.text-secondary { + color: $textSecondary; +} + +small { + font-size: 75%; +} + +.ellipsis { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/app/scss/variables.scss b/app/scss/variables.scss new file mode 100644 index 0000000..d856cac --- /dev/null +++ b/app/scss/variables.scss @@ -0,0 +1,10 @@ +@import '~open-color/open-color.scss'; + +$dark1: lighten(#0e0e0e, 20%); +$dark2: lighten(#0e0e0e, 15%); +$dark3: lighten(#0e0e0e, 10%); +$dark4: lighten(#0e0e0e, 5%); +$dark5: #0e0e0e; + +$textPrimary: $oc-gray-3; +$textSecondary: $oc-gray-5; diff --git a/app/services/item-text.service.ts b/app/services/item-text.service.ts index b47fca3..3d68f82 100644 --- a/app/services/item-text.service.ts +++ b/app/services/item-text.service.ts @@ -63,14 +63,16 @@ const getSockets = (sockets: any[]): string => { return content.trim() + '\n'; }; +/** filter unwanted characters from item name */ const filterName = (name: string): string => { - const index = name.lastIndexOf('>'); + const newString = name.replace(/>/g, '>'); + const index = newString.lastIndexOf('>'); if (index < 0) { return name; } return name - .slice(index + 4, name.length) + .slice(index + 1, name.length) .replace('{', '') .replace('}', ''); }; @@ -103,5 +105,6 @@ const getProperties = (p?: any[], precedingText?: string): string => { }; export const ItemTextService = { + filterName, parseItem, }; diff --git a/app/services/poe.service.ts b/app/services/poe.service.ts index b2e4ea3..beb2e1a 100644 --- a/app/services/poe.service.ts +++ b/app/services/poe.service.ts @@ -1,20 +1,44 @@ +import PQueue from 'p-queue'; +import { POE_HOME, POE_LEAGUE_LIST_URL, POE_STASH_ITEMS_URL } from '../constants'; import { http } from '../http'; -export class PoeService { - public static async getStash(): Promise { - const res = await http.get( - 'https://pathofexile.com/character-window/get-stash-items?' + - 'league=Incursion&tabs=1&tabIndex=6&accountName=DoctorCoctor', - ); - return res.data; - } +const queue = new PQueue({ concurrency: 1 }); - public static async priceCheck(item: any) { - const res = await http.get(`https://poeprices.info/api?l=Incursion&i=${encodeURI(btoa(item))}`, { - headers: { - 'Cache-Control': 'max-age=600', - }, +const getStash = async (username: string, league: string, tabIndex: number | string): Promise => { + const res = await http.get( + `${POE_STASH_ITEMS_URL}?league=${league}&tabs=1&tabIndex=${tabIndex}&accountName=${username}`, + ); + return res.data; +}; + +const priceCheck = async (item: any): Promise => { + return new Promise(resolve => { + queue.add(() => { + return http + .get(`https://poeprices.info/api?l=Incursion&i=${encodeURI(btoa(item))}`, { + headers: { + 'Cache-Control': 'max-age=600', + }, + }) + .then(resolve); }); - return res.data; - } -} + }); +}; + +const getUsername = async (): Promise => { + const res = await http.get(POE_HOME); + const username = res.data.match(/\/account\/view-profile\/(.*?)\"/); + return username[1]; +}; + +const getLeagues = async (): Promise => { + const res = await http.get(POE_LEAGUE_LIST_URL); + return res.data; +}; + +export const PoeService = { + getLeagues, + getStash, + getUsername, + priceCheck, +}; diff --git a/app/services/storage.service.ts b/app/services/storage.service.ts new file mode 100644 index 0000000..eeeddfd --- /dev/null +++ b/app/services/storage.service.ts @@ -0,0 +1,25 @@ +import Store from 'electron-store'; +const store = new Store(); + +const storeUsername = (username: string) => { + store.set('user', username); +}; + +const storeSessionID = (sessionID: string) => { + store.set('sessionID', sessionID); +}; + +const getSessionID = (): string | undefined => { + return store.get('sessionID') || undefined; +}; + +const getUsername = (): string | undefined => { + return store.get('user') || undefined; +}; + +export const StorageService = { + getUsername, + getSessionID, + storeUsername, + storeSessionID, +}; diff --git a/app/stores/app.store.ts b/app/stores/app.store.ts new file mode 100644 index 0000000..0f25156 --- /dev/null +++ b/app/stores/app.store.ts @@ -0,0 +1,114 @@ +import Store from 'electron-store'; +import { action, computed, observable } from 'mobx'; +import { setHeader } from '../http'; +import { ILeague } from '../model'; +import { IItem } from '../model/item'; +import { IStashTab } from '../model/stash-tab'; +import { PoeService } from '../services'; +import { StorageService } from '../services/storage.service'; +const store = new Store(); + +export class AppStore { + @observable + public username?: string; + @observable + public sessionID?: string; + @observable + public activeLeague: ILeague; + @observable + public selectedTabIndex: number = 0; + @observable + public stashTabs: IStashTab[] = []; + @observable + public stashItems: IItem[] = []; + @observable + public leagues: ILeague[] = []; + @observable + public appReady: boolean; + + constructor() { + this.bootstrap(); + } + + private async bootstrap() { + try { + this.username = StorageService.getUsername(); + this.sessionID = StorageService.getSessionID(); + if (this.sessionID) { + await this.loadLeagues(); + } + } catch (e) { + console.error(e); + } + this.appReady = true; + } + + @action + private async loadLeagues() { + try { + const leagues = await PoeService.getLeagues(); + if (!leagues) { + return; + } + this.leagues = leagues; + // set active league if not set + if (!this.activeLeague && this.leagues!.length > 0) { + this.setActiveLeague(this.leagues[0].id); + } + } catch (e) { + console.error(e); + } + } + + @computed + public get isLoggedIn() { + return this.username && this.sessionID; + } + + @action + public resetState() { + delete this.username; + delete this.sessionID; + setHeader('Cookie', ''); + store.clear(); + } + + @action + public setSessionID(sessionID: string) { + setHeader('Cookie', `POESESSID=${sessionID}`); + this.sessionID = sessionID; + StorageService.storeSessionID(sessionID); + } + + @action + public setUsername(username: string) { + this.username = username; + StorageService.storeUsername(username); + } + + @action + public setActiveLeague(id: string) { + const league = this.leagues.find(l => l.id === id); + if (league) { + this.activeLeague = league; + this.stashTabs = []; + this.selectedTabIndex = 0; + this.stashItems = []; + this.loadItems(this.selectedTabIndex); + } + } + + @action + public async performLogin(sessionID: string) { + this.setSessionID(sessionID); + const username = await PoeService.getUsername(); + this.setUsername(username); + } + + @action + public async loadItems(tabIndex: number | string) { + const data = await PoeService.getStash(this.username!, this.activeLeague.id, tabIndex); + this.stashTabs = data.tabs; + this.stashItems = data.items; + } +} diff --git a/app/stores/root.store.ts b/app/stores/root.store.ts new file mode 100644 index 0000000..30c282b --- /dev/null +++ b/app/stores/root.store.ts @@ -0,0 +1,5 @@ +import { AppStore } from './app.store'; + +export class RootStore { + public appStore = new AppStore(); +} diff --git a/app/wrapper.tsx b/app/wrapper.tsx new file mode 100644 index 0000000..2f26d69 --- /dev/null +++ b/app/wrapper.tsx @@ -0,0 +1,32 @@ +import { inject, observer } from 'mobx-react'; +import React from 'react'; +import { Home, LoginWebview } from './components'; +import { AppStore } from './stores/app.store'; + +interface IProps { + appStore?: AppStore; +} + +interface IState {} + +@inject('appStore') +@observer +export class Wrapper extends React.Component { + constructor(props: any) { + super(props); + } + + public render() { + const { appStore } = this.props; + + if (!appStore!.appReady) { + return null; + } + + if (!appStore!.isLoggedIn) { + return ; + } + + return ; + } +} diff --git a/index.html b/index.html index d668b80..1dc15ef 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ + diff --git a/package-lock.json b/package-lock.json index 4d1bc7b..40c88da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -84,6 +84,14 @@ "vue-template-es2015-compiler": "1.6.0" } }, + "@types/electron-store": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@types/electron-store/-/electron-store-1.3.0.tgz", + "integrity": "sha512-PplQbntPl3xNcckjizjoQShPwLot72jFiaI67CZ0JOr+AuGwqXdL73sU3vfuuP+RZecOh8vX1G7yWjmxs4lvBQ==", + "requires": { + "@types/node": "7.0.69" + } + }, "@types/lodash": { "version": "4.14.116", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.116.tgz", @@ -94,6 +102,11 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.69.tgz", "integrity": "sha512-S5NC8HV6HnRipg8nC0j30TPl7ktXjRTKqgyINLNe8K/64UJUI8Lq0sRopXC0hProsV2F5ibj8IqPkl1xpGggrw==" }, + "@types/p-queue": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@types/p-queue/-/p-queue-2.3.1.tgz", + "integrity": "sha512-JyO7uMAtkcMMULmsTQ4t/lCC8nxirTtweGG1xAFNNIAoC1RemmeIxq8PiKghuEy99XdbS6Lwx4zpbXUjfeSSAA==" + }, "@types/prop-types": { "version": "15.5.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.5.tgz", @@ -2621,6 +2634,18 @@ } } }, + "conf": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/conf/-/conf-2.0.0.tgz", + "integrity": "sha512-iCLzBsGFi8S73EANsEJZz0JnJ/e5VZef/kSaxydYZLAvw0rFNAUx5R7K5leC/CXXR2mZfXWhUvcZOO/dM2D5xg==", + "requires": { + "dot-prop": "4.2.0", + "env-paths": "1.0.0", + "make-dir": "1.3.0", + "pkg-up": "2.0.0", + "write-file-atomic": "2.3.0" + } + }, "console-browserify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", @@ -3523,6 +3548,14 @@ "domelementtype": "1.3.0" } }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "requires": { + "is-obj": "1.0.1" + } + }, "ds-store": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ds-store/-/ds-store-0.1.6.tgz", @@ -4727,6 +4760,14 @@ "debug": "2.6.9" } }, + "electron-store": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-2.0.0.tgz", + "integrity": "sha512-1WCFYHsYvZBqDsoaS0Relnz0rd81ZkBAI0Fgx7Nq2UWU77rSNs1qxm4S6uH7TCZ0bV3LQpJFk7id/is/ZgoOPA==", + "requires": { + "conf": "2.0.0" + } + }, "electron-to-chromium": { "version": "1.3.58", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.58.tgz", @@ -5055,8 +5096,7 @@ "env-paths": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz", - "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=", - "dev": true + "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=" }, "errno": { "version": "0.1.7", @@ -6735,6 +6775,11 @@ "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", "dev": true }, + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", @@ -7350,6 +7395,11 @@ "kind-of": "3.2.2" } }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -8611,6 +8661,20 @@ } } }, + "mobx": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-5.1.0.tgz", + "integrity": "sha512-cQzBmF2gbpcBR2xKwt5jZx0ncDMC9Hg5nYrp3Krv0qHH4GFqWZyR+ZLkRJRcvVF4rqxpy2G96Qbk5YzyQ8tdXw==" + }, + "mobx-react": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.2.5.tgz", + "integrity": "sha512-vSwsjGwmaqTmaEsPWET/APccjirTiIIchQA3YVasKzaxIGv62BNJUHFjrkIAbGBLeqJma+ZgSu158OqQLK0vaQ==", + "requires": { + "hoist-non-react-statics": "2.5.5", + "react-lifecycles-compat": "3.0.4" + } + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -9326,6 +9390,11 @@ "p-limit": "1.3.0" } }, + "p-queue": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-2.4.2.tgz", + "integrity": "sha512-n8/y+yDJwBjoLQe1GSJbbaYQLTI7QHNZI2+rpmCDbe++WLf9HC3gf6iqj5yfPAV71W4UF3ql5W1+UBPXoXTxng==" + }, "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", @@ -9536,6 +9605,24 @@ } } }, + "pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "requires": { + "find-up": "2.1.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "2.0.0" + } + } + } + }, "plist": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-2.1.0.tgz", @@ -10494,6 +10581,11 @@ "prop-types": "15.6.2" } }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -13504,6 +13596,16 @@ "dev": true, "optional": true }, + "write-file-atomic": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", + "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "requires": { + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "signal-exit": "3.0.2" + } + }, "xml-name-validator": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", diff --git a/package.json b/package.json index 234770f..565a6e4 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,9 @@ } }, "dependencies": { + "@types/electron-store": "^1.3.0", "@types/lodash": "^4.14.116", + "@types/p-queue": "^2.3.1", "@types/react": "^16.4.9", "@types/react-dom": "^16.0.7", "autoprefixer": "^9.1.1", @@ -61,13 +63,17 @@ "electron": "^2.0.7", "electron-compile": "^6.4.3", "electron-squirrel-startup": "^1.0.0", + "electron-store": "^2.0.0", "file-loader": "^1.1.11", "font-awesome": "^4.7.0", "html-webpack-plugin": "^3.2.0", "lodash": "^4.17.10", + "mobx": "^5.1.0", + "mobx-react": "^5.2.5", "node-sass": "^4.9.3", "normalize.css": "^8.0.0", "open-color": "^1.6.3", + "p-queue": "^2.4.2", "postcss-loader": "^3.0.0", "react": "^16.4.2", "react-dom": "^16.4.2", diff --git a/tslint.json b/tslint.json index f14d462..ce2bbb3 100644 --- a/tslint.json +++ b/tslint.json @@ -3,7 +3,7 @@ "rules": { "semicolon": [true, "always", "ignore-bound-class-methods"], "max-line-length": [true, 120], - "variable-name": [true, "ban-keywords", "check-format", "allow-pascal-case"], + "variable-name": [false, "ban-keywords", "check-format", "allow-pascal-case"], "import-name": false, "ter-arrow-parens": [true, "as-needed"], "align": [true, "parameters", "statements"]