mirror of
https://github.com/teoxoy/factorio-blueprint-editor.git
synced 2025-01-14 02:23:21 +02:00
extract website from the editor
parcel doesn't fully support yarn workspaces (parcel-bundler/parcel#2269) migrate to webpack configure monorepo for the new changes
This commit is contained in:
parent
ddeb2c0225
commit
22b39627b7
@ -1,11 +1,16 @@
|
||||
const glob = require('glob')
|
||||
const configs = glob.sync('./packages/**/tsconfig.json')
|
||||
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true
|
||||
es6: true,
|
||||
node: true
|
||||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: './tsconfig.json'
|
||||
project: configs[0],
|
||||
projects: configs
|
||||
},
|
||||
plugins: ['@typescript-eslint'],
|
||||
extends: [
|
||||
|
0
logo.svg → .github/logo.svg
vendored
0
logo.svg → .github/logo.svg
vendored
Before Width: | Height: | Size: 487 KiB After Width: | Height: | Size: 487 KiB |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,2 @@
|
||||
node_modules
|
||||
.cache
|
||||
dist
|
||||
|
@ -15,6 +15,7 @@ You can file new issues by selecting from our [new issue templates](https://gith
|
||||
## Prerequisites
|
||||
|
||||
- [git](https://git-scm.com/)
|
||||
- [yarn](https://yarnpkg.com)
|
||||
- [node](https://nodejs.org/en/)
|
||||
- [vscode](https://code.visualstudio.com/)
|
||||
|
||||
@ -28,13 +29,13 @@ This project uses `eslint` and `prettier` to lint and format code. I would recom
|
||||
1. Clone your fork
|
||||
1. Download the recommended workspace extensions in vscode
|
||||
1. Make your changes in a new git branch (`git checkout -b my-fix-branch master`)
|
||||
1. Run `npm install`
|
||||
1. Run `npm start`
|
||||
1. Open the link in a browser or use the vscode debugger
|
||||
1. Make changes
|
||||
1. Commit your changes using a descriptive commit message
|
||||
1. Push your branch to GitHub `git push origin my-fix-branch`
|
||||
1. Start a pull request from GitHub
|
||||
2. Run `yarn`
|
||||
3. Run `yarn start`
|
||||
4. Open the link in a browser or use the vscode debugger
|
||||
5. Make changes
|
||||
6. Commit your changes using a descriptive commit message
|
||||
7. Push your branch to GitHub `git push origin my-fix-branch`
|
||||
8. Start a pull request from GitHub
|
||||
|
||||
That's it! 🎉 Thank you for your contribution! 😃
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<img src="./logo.svg" width="100%" align="right">
|
||||
<img src="./.github/logo.svg" width="100%" align="right">
|
||||
|
||||
# factorio-blueprint-editor
|
||||
|
||||
|
10
package.json
10
package.json
@ -3,13 +3,23 @@
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"start:editor": "yarn workspace @fbe/editor run start",
|
||||
"start:website": "yarn workspace @fbe/website run start",
|
||||
"build:editor": "yarn workspace @fbe/editor run build",
|
||||
"build:website": "yarn workspace @fbe/website run build",
|
||||
"start": "yarn build:editor && concurrently \"yarn:start:editor\" \"yarn:start:website\"",
|
||||
"build": "yarn build:editor && yarn build:website"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^1.13.0",
|
||||
"@typescript-eslint/parser": "^1.13.0",
|
||||
"concurrently": "^4.1.1",
|
||||
"eslint": "^6.1.0",
|
||||
"eslint-config-prettier": "^6.0.0",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"eslint-plugin-prettier": "^3.1.0",
|
||||
"glob": "^7.1.4",
|
||||
"prettier": "1.18.2",
|
||||
"typescript": "^3.5.3"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "factorio-blueprint-editor",
|
||||
"version": "0.1.0",
|
||||
"name": "@fbe/editor",
|
||||
"version": "1.0.0",
|
||||
"description": "A Factorio blueprint editor and renderer webapp",
|
||||
"author": "Teoxoy",
|
||||
"license": "MIT",
|
||||
@ -10,41 +10,29 @@
|
||||
},
|
||||
"bugs": "https://github.com/Teoxoy/factorio-blueprint-editor/issues",
|
||||
"homepage": "https://github.com/Teoxoy/factorio-blueprint-editor",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
"scripts": {
|
||||
"start": "parcel src/index.html --port 8080",
|
||||
"prebuild": "rimraf dist/*",
|
||||
"build": "parcel build src/index.html --public-url ./",
|
||||
"setupDist": "git worktree add dist gh-pages",
|
||||
"deploy": "cd dist && git add --all && git reset -- stats && git commit -m \"new version\" && git push origin gh-pages"
|
||||
"start": "concurrently \"tsc -w\" \"ts-cleaner -w\"",
|
||||
"build": "tsc && ts-cleaner"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 2 Chrome versions",
|
||||
"last 2 Firefox versions",
|
||||
"last 2 Safari versions",
|
||||
"last 2 Edge versions"
|
||||
],
|
||||
"dependencies": {
|
||||
"ajv": "^6.10.2",
|
||||
"dat.gui": "^0.7.6",
|
||||
"delaunator": "^4.0.0",
|
||||
"eventemitter3": "^4.0.0",
|
||||
"factorio-data": "4.0.4",
|
||||
"file-saver": "^2.0.2",
|
||||
"keyboardjs": "^2.5.1",
|
||||
"pako": "^1.0.10",
|
||||
"pathfinding": "^0.4.18",
|
||||
"pixi.js": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dat.gui": "^0.7.3",
|
||||
"@types/delaunator": "^3.0.0",
|
||||
"@types/file-saver": "^2.0.1",
|
||||
"@types/keyboardjs": "^2.4.1",
|
||||
"@types/pako": "^1.0.1",
|
||||
"@types/pathfinding": "0.0.3",
|
||||
"parcel-bundler": "^1.12.3",
|
||||
"rimraf": "^2.6.3",
|
||||
"stylus": "^0.54.5",
|
||||
"concurrently": "^4.1.1",
|
||||
"ts-cleaner": "^1.0.2",
|
||||
"typescript": "^3.5.3"
|
||||
}
|
||||
}
|
||||
|
@ -5,12 +5,12 @@ import G from '../../common/globals'
|
||||
export default class Checkbox extends PIXI.Container {
|
||||
/** Checkmark Polygon */
|
||||
// prettier-ignore
|
||||
private static readonly CHECK_POLYGON: PIXI.Polygon = new PIXI.Polygon(
|
||||
private static readonly CHECK_POLYGON: PIXI.Polygon = new PIXI.Polygon([
|
||||
8, 8, 12, 8, 16, 12, 20, 12, 24, 8,
|
||||
28, 8, 28, 12, 24, 16, 24, 20, 28, 24,
|
||||
28, 28, 24, 28, 20, 24, 16, 24, 12, 28,
|
||||
8, 28, 8, 24, 12, 20, 12, 16, 8, 12,
|
||||
8, 8)
|
||||
8, 8])
|
||||
|
||||
/**
|
||||
* Draw Checkbox Graphic
|
||||
|
@ -52,7 +52,7 @@ export default class Preview extends PIXI.Container {
|
||||
private generatePreview(): PIXI.Container {
|
||||
// Add all entity parts to a separate container
|
||||
const entityParts: PIXI.Container = new PIXI.Container()
|
||||
EntitySprite.getParts(this.m_Entity, G.quality.hr).forEach(s => entityParts.addChild(s))
|
||||
EntitySprite.getParts(this.m_Entity).forEach(s => entityParts.addChild(s))
|
||||
this.addChild(entityParts)
|
||||
|
||||
const actualSpriteSize = { x: this.m_Entity.size.x, y: this.m_Entity.size.y }
|
||||
|
@ -1,5 +1,4 @@
|
||||
import * as PIXI from 'pixi.js'
|
||||
import G from '../common/globals'
|
||||
import Entity from '../factorio-data/entity'
|
||||
import { DebugContainer } from './panels/debug'
|
||||
import { QuickbarContainer } from './panels/quickbar'
|
||||
@ -18,7 +17,7 @@ export default class UIContainer extends PIXI.Container {
|
||||
super()
|
||||
|
||||
this.debugContainer = new DebugContainer()
|
||||
this.quickbarContainer = new QuickbarContainer(G.quickbarRows)
|
||||
this.quickbarContainer = new QuickbarContainer(2)
|
||||
this.infoEntityPanel = new InfoEntityPanel()
|
||||
this.dialogsContainer = new PIXI.Container()
|
||||
this.paintIconContainer = new PIXI.Container()
|
||||
@ -60,12 +59,12 @@ export default class UIContainer extends PIXI.Container {
|
||||
this.dialogsContainer.addChild(inv)
|
||||
}
|
||||
|
||||
public changeQuickbarRows(rows: number): void {
|
||||
const itemNames = this.quickbarContainer.serialize()
|
||||
this.quickbarContainer.destroy()
|
||||
this.quickbarContainer = new QuickbarContainer(rows, itemNames)
|
||||
// public changeQuickbarRows(rows: number): void {
|
||||
// const itemNames = this.quickbarContainer.serialize()
|
||||
// this.quickbarContainer.destroy()
|
||||
// this.quickbarContainer = new QuickbarContainer(rows, itemNames)
|
||||
|
||||
const index = this.getChildIndex(this.quickbarContainer)
|
||||
this.addChildAt(this.quickbarContainer, index)
|
||||
}
|
||||
// const index = this.getChildIndex(this.quickbarContainer)
|
||||
// this.addChildAt(this.quickbarContainer, index)
|
||||
// }
|
||||
}
|
||||
|
@ -1,36 +1,46 @@
|
||||
import keyboardJS from 'keyboardjs'
|
||||
|
||||
// Set the general application keyboard context
|
||||
// Needed to have seperate context's for input controls (i.e. Textbox)
|
||||
keyboardJS.setContext('editor')
|
||||
function initActions(canvas: HTMLCanvasElement): void {
|
||||
// Set the general application keyboard context
|
||||
// Needed to have seperate context's for input controls (i.e. Textbox)
|
||||
keyboardJS.setContext('editor')
|
||||
|
||||
const canvasEl = document.getElementById('editor') as HTMLCanvasElement
|
||||
// Bind the events on the canvas
|
||||
// @ts-ignore
|
||||
keyboardJS.watch(canvas)
|
||||
|
||||
// Bind the events on the canvas
|
||||
keyboardJS.watch(canvasEl)
|
||||
// keyboardJS.watch will bind keydown and keyup events on the canvas but
|
||||
// keydown and keyup will only fire if the canvas is focused
|
||||
canvas.addEventListener('mouseover', () => canvas.focus())
|
||||
canvas.addEventListener('blur', () => {
|
||||
keyboardJS.releaseAllKeys()
|
||||
})
|
||||
|
||||
// keyboardJS.watch will bind keydown and keyup events on the canvas but
|
||||
// keydown and keyup will only fire if the canvas is focused
|
||||
canvasEl.addEventListener('mouseover', () => canvasEl.focus())
|
||||
canvasEl.addEventListener('blur', () => {
|
||||
keyboardJS.releaseAllKeys()
|
||||
})
|
||||
// Hack for plugging the mouse into keyboardJS
|
||||
// @ts-ignore
|
||||
keyboardJS._locale.bindKeyCode(300, ['lclick'])
|
||||
// @ts-ignore
|
||||
keyboardJS._locale.bindKeyCode(301, ['mclick'])
|
||||
// @ts-ignore
|
||||
keyboardJS._locale.bindKeyCode(302, ['rclick'])
|
||||
// @ts-ignore
|
||||
keyboardJS._locale.bindKeyCode(303, ['wheelNeg'])
|
||||
// @ts-ignore
|
||||
keyboardJS._locale.bindKeyCode(304, ['wheelPos'])
|
||||
// @ts-ignore
|
||||
canvas.addEventListener('mousedown', e => keyboardJS.pressKey(e.button + 300, e))
|
||||
// attach mouseup to window so that releaseKey will be called even when mouseup is fired from outside the window
|
||||
// @ts-ignore
|
||||
window.addEventListener('mouseup', e => keyboardJS.releaseKey(e.button + 300, e))
|
||||
|
||||
// Hack for plugging the mouse into keyboardJS
|
||||
keyboardJS._locale.bindKeyCode(300, ['lclick'])
|
||||
keyboardJS._locale.bindKeyCode(301, ['mclick'])
|
||||
keyboardJS._locale.bindKeyCode(302, ['rclick'])
|
||||
keyboardJS._locale.bindKeyCode(303, ['wheelNeg'])
|
||||
keyboardJS._locale.bindKeyCode(304, ['wheelPos'])
|
||||
canvasEl.addEventListener('mousedown', e => keyboardJS.pressKey(e.button + 300, e))
|
||||
// attach mouseup to window so that releaseKey will be called even when mouseup is fired from outside the window
|
||||
window.addEventListener('mouseup', e => keyboardJS.releaseKey(e.button + 300, e))
|
||||
|
||||
canvasEl.addEventListener('wheel', e => {
|
||||
e.preventDefault()
|
||||
keyboardJS.pressKey(Math.sign(-e.deltaY) === 1 ? 303 : 304, e)
|
||||
keyboardJS.releaseKey(Math.sign(-e.deltaY) === 1 ? 303 : 304, e)
|
||||
})
|
||||
canvas.addEventListener('wheel', e => {
|
||||
e.preventDefault()
|
||||
// @ts-ignore
|
||||
keyboardJS.pressKey(Math.sign(-e.deltaY) === 1 ? 303 : 304, e)
|
||||
// @ts-ignore
|
||||
keyboardJS.releaseKey(Math.sign(-e.deltaY) === 1 ? 303 : 304, e)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes trough all events to the callback
|
||||
@ -49,6 +59,7 @@ function passtroughAllEvents(cb: (e: keyboardJS.KeyEvent) => boolean): void {
|
||||
}
|
||||
|
||||
class Action {
|
||||
public readonly name: string
|
||||
private readonly defaultKeyCombo: string
|
||||
|
||||
private m_active = true
|
||||
@ -59,7 +70,8 @@ class Action {
|
||||
}[] = []
|
||||
private _pressed = false
|
||||
|
||||
public constructor(defaultKeyCombo: string) {
|
||||
public constructor(name: string, defaultKeyCombo: string) {
|
||||
this.name = name
|
||||
this.defaultKeyCombo = defaultKeyCombo
|
||||
this.m_keyCombo = defaultKeyCombo
|
||||
|
||||
@ -73,6 +85,13 @@ class Action {
|
||||
})
|
||||
}
|
||||
|
||||
public get prettyName(): string {
|
||||
return this.name
|
||||
.split(/(?=[A-Z1-9])/)
|
||||
.join(' ')
|
||||
.replace(/(\b\w)/, c => c.toUpperCase())
|
||||
}
|
||||
|
||||
private get active(): boolean {
|
||||
return this.m_active
|
||||
}
|
||||
@ -187,118 +206,68 @@ class Action {
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
clear: new Action('shift+n'),
|
||||
focus: new Action('f'),
|
||||
takePicture: new Action('modifier+s'),
|
||||
undo: new Action('modifier+z'),
|
||||
redo: new Action('modifier+y'),
|
||||
info: new Action('i'),
|
||||
pan: new Action('lclick'),
|
||||
zoomIn: new Action('wheelNeg'),
|
||||
zoomOut: new Action('wheelPos'),
|
||||
moveEntityUp: new Action('up'),
|
||||
moveEntityLeft: new Action('left'),
|
||||
moveEntityDown: new Action('down'),
|
||||
moveEntityRight: new Action('right'),
|
||||
const actions = new Map<string, Action>()
|
||||
|
||||
generateOilOutpost: new Action('g'),
|
||||
copySelection: new Action('modifier+lclick'),
|
||||
deleteSelection: new Action('modifier+rclick'),
|
||||
function registerAction(name: string, keyCombo: string): Action {
|
||||
const action = new Action(name, keyCombo)
|
||||
actions.set(name, action)
|
||||
return action
|
||||
}
|
||||
|
||||
moveUp: new Action('w'),
|
||||
moveLeft: new Action('a'),
|
||||
moveDown: new Action('s'),
|
||||
moveRight: new Action('d'),
|
||||
|
||||
inventory: new Action('e'),
|
||||
build: new Action('lclick'),
|
||||
mine: new Action('rclick'),
|
||||
copyEntitySettings: new Action('shift+rclick'),
|
||||
pasteEntitySettings: new Action('shift+lclick'),
|
||||
/** Used for highlighting the source entity */
|
||||
tryPasteEntitySettings: new Action('shift'),
|
||||
openEntityGUI: new Action('lclick'),
|
||||
showInfo: new Action('alt'),
|
||||
pipette: new Action('q'),
|
||||
rotate: new Action('r'),
|
||||
reverseRotate: new Action('shift+r'),
|
||||
closeWindow: new Action('esc'),
|
||||
|
||||
increaseTileBuildingArea: new Action(']'),
|
||||
decreaseTileBuildingArea: new Action('['),
|
||||
|
||||
quickbar1: new Action('1'),
|
||||
quickbar2: new Action('2'),
|
||||
quickbar3: new Action('3'),
|
||||
quickbar4: new Action('4'),
|
||||
quickbar5: new Action('5'),
|
||||
quickbar6: new Action('shift+1'),
|
||||
quickbar7: new Action('shift+2'),
|
||||
quickbar8: new Action('shift+3'),
|
||||
quickbar9: new Action('shift+4'),
|
||||
quickbar10: new Action('shift+5'),
|
||||
changeActiveQuickbar: new Action('x'),
|
||||
|
||||
copyBPString: {
|
||||
bind: (opts: { press?: (e: ClipboardEvent) => void }) => {
|
||||
if (opts.press === undefined) {
|
||||
return
|
||||
}
|
||||
document.addEventListener('copy', (e: ClipboardEvent) => {
|
||||
if (document.activeElement !== canvasEl) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
opts.press(e)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
pasteBPString: {
|
||||
bind: (opts: { press?: (e: ClipboardEvent) => void }) => {
|
||||
if (opts.press === undefined) {
|
||||
return
|
||||
}
|
||||
document.addEventListener('paste', (e: ClipboardEvent) => {
|
||||
if (document.activeElement !== canvasEl) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
opts.press(e)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
forEachAction(cb: (action: Action, actionName: string) => void) {
|
||||
for (const actionName in this) {
|
||||
if (this[actionName] instanceof Action) {
|
||||
cb(this[actionName], actionName)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
importKeybinds(keybinds: Record<string, string>) {
|
||||
if (!keybinds) {
|
||||
return
|
||||
}
|
||||
actions.forEachAction((action, actionName) => {
|
||||
if (keybinds[actionName] !== undefined) {
|
||||
action.keyCombo = keybinds[actionName]
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
exportKeybinds(changedOnly = true) {
|
||||
const changedKeybinds: Record<string, string> = {}
|
||||
actions.forEachAction((action, actionName) => {
|
||||
if (!changedOnly || !action.usesDefaultKeyCombo) {
|
||||
changedKeybinds[actionName] = action.keyCombo
|
||||
}
|
||||
})
|
||||
return changedKeybinds
|
||||
function callAction(name: string): void {
|
||||
const action = actions.get(name)
|
||||
if (action) {
|
||||
action.call()
|
||||
}
|
||||
}
|
||||
|
||||
export { passtroughAllEvents }
|
||||
export default actions
|
||||
function isActionActive(name: string): boolean {
|
||||
const action = actions.get(name)
|
||||
if (action) {
|
||||
return action.pressed
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function forEachAction(cb: (action: Action, actionName: string) => void): void {
|
||||
actions.forEach((action, name) => cb(action, name))
|
||||
}
|
||||
|
||||
function resetKeybinds(): void {
|
||||
actions.forEach(action => {
|
||||
action.resetKeyCombo()
|
||||
})
|
||||
}
|
||||
|
||||
function importKeybinds(keybinds: Record<string, string>): void {
|
||||
if (!keybinds) {
|
||||
return
|
||||
}
|
||||
actions.forEach((action, name) => {
|
||||
if (keybinds[name] !== undefined) {
|
||||
action.keyCombo = keybinds[name]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function exportKeybinds(changedOnly = true): Record<string, string> {
|
||||
const changedKeybinds: Record<string, string> = {}
|
||||
actions.forEach((action, name) => {
|
||||
if (!changedOnly || !action.usesDefaultKeyCombo) {
|
||||
changedKeybinds[name] = action.keyCombo
|
||||
}
|
||||
})
|
||||
return changedKeybinds
|
||||
}
|
||||
|
||||
export {
|
||||
initActions,
|
||||
passtroughAllEvents,
|
||||
registerAction,
|
||||
callAction,
|
||||
isActionActive,
|
||||
forEachAction,
|
||||
resetKeybinds,
|
||||
importKeybinds,
|
||||
exportKeybinds
|
||||
}
|
||||
|
@ -1,43 +1,15 @@
|
||||
import * as PIXI from 'pixi.js'
|
||||
import Blueprint from '../factorio-data/blueprint'
|
||||
import { BlueprintContainer } from '../containers/blueprint'
|
||||
import { Book } from '../factorio-data/book'
|
||||
import UIContainer from '../UI/ui'
|
||||
|
||||
const hr = false
|
||||
let debug = false
|
||||
|
||||
const quality = {
|
||||
hr: true,
|
||||
compressed: true
|
||||
}
|
||||
|
||||
// this is how it works in factorio but js doesn't support 64bit bitwise operations
|
||||
// uint64_t(developerVersion) |
|
||||
// (uint64_t(minorVersion) << 16) |
|
||||
// (uint64_t(majorVersion) << 32) |
|
||||
// (uint64_t(mainVersion) << 48)
|
||||
const getFactorioVersion = (main = 0, major = 17, minor = 14): number =>
|
||||
(minor << 16) + (major | (main << 16)) * 0xffffffff
|
||||
|
||||
let app: PIXI.Application
|
||||
let BPC: BlueprintContainer
|
||||
let UI: UIContainer
|
||||
|
||||
const loadingScreen = {
|
||||
el: document.getElementById('loadingScreen'),
|
||||
show() {
|
||||
this.el.classList.add('active')
|
||||
},
|
||||
hide() {
|
||||
this.el.classList.remove('active')
|
||||
}
|
||||
}
|
||||
|
||||
let moveSpeed = 10
|
||||
let quickbarRows = 2
|
||||
|
||||
let bp: Blueprint
|
||||
let book: Book
|
||||
|
||||
const colors = {
|
||||
text: {
|
||||
@ -96,23 +68,6 @@ const colors = {
|
||||
},
|
||||
quickbar: {
|
||||
background: { color: 0x303030, alpha: 1, border: 2 }
|
||||
},
|
||||
|
||||
_darkTheme: true,
|
||||
_tintsToChange: [] as PIXI.Sprite[],
|
||||
pattern: 'grid' as 'checker' | 'grid',
|
||||
get darkTheme() {
|
||||
return this._darkTheme
|
||||
},
|
||||
set darkTheme(value: boolean) {
|
||||
this._darkTheme = value
|
||||
this._tintsToChange.forEach((s: PIXI.Sprite) => {
|
||||
s.tint = value ? 0x303030 : 0xc9c9c9
|
||||
})
|
||||
},
|
||||
addSpriteForAutomaticTintChange(sprite: PIXI.Sprite) {
|
||||
sprite.tint = this.darkTheme ? 0x303030 : 0xc9c9c9
|
||||
this._tintsToChange.push(sprite)
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,16 +136,11 @@ const styles = {
|
||||
|
||||
export default {
|
||||
debug,
|
||||
quality,
|
||||
getFactorioVersion,
|
||||
hr,
|
||||
BPC,
|
||||
UI,
|
||||
app,
|
||||
bp,
|
||||
book,
|
||||
moveSpeed,
|
||||
quickbarRows,
|
||||
loadingScreen,
|
||||
colors,
|
||||
fontFamily,
|
||||
styles
|
||||
|
@ -2,7 +2,7 @@ import FD from 'factorio-data'
|
||||
import { EventEmitter } from 'eventemitter3'
|
||||
import * as PIXI from 'pixi.js'
|
||||
import G from '../common/globals'
|
||||
import actions from '../actions'
|
||||
import { isActionActive, callAction } from '../actions'
|
||||
import Entity from '../factorio-data/entity'
|
||||
import Tile from '../factorio-data/tile'
|
||||
import { Viewport } from '../viewport'
|
||||
@ -123,7 +123,12 @@ class OptimizedContainer extends PIXI.ParticleContainer {
|
||||
}
|
||||
}
|
||||
|
||||
type GridPattern = 'checker' | 'grid'
|
||||
|
||||
class BlueprintContainer extends PIXI.Container {
|
||||
public moveSpeed = 10
|
||||
private _gridColor = 0x303030
|
||||
private _gridPattern: GridPattern = 'grid'
|
||||
private grid: PIXI.TilingSprite
|
||||
private chunkGrid: PIXI.TilingSprite
|
||||
public wiresContainer: WiresContainer
|
||||
@ -180,7 +185,7 @@ class BlueprintContainer extends PIXI.Container {
|
||||
3
|
||||
)
|
||||
|
||||
this.generateGrid(G.colors.pattern)
|
||||
this.generateGrid()
|
||||
this.tileSprites = new OptimizedContainer()
|
||||
this.tilePaintSlot = new PIXI.Container()
|
||||
this.visualizationAreaContainer = new VisualizationAreaContainer()
|
||||
@ -205,14 +210,14 @@ class BlueprintContainer extends PIXI.Container {
|
||||
|
||||
G.app.ticker.add(() => {
|
||||
if (this.mode !== EditorMode.PAN) {
|
||||
const WSXOR = actions.moveUp.pressed !== actions.moveDown.pressed
|
||||
const ADXOR = actions.moveLeft.pressed !== actions.moveRight.pressed
|
||||
const WSXOR = isActionActive('moveUp') !== isActionActive('moveDown')
|
||||
const ADXOR = isActionActive('moveLeft') !== isActionActive('moveRight')
|
||||
if (WSXOR || ADXOR) {
|
||||
const finalSpeed = G.moveSpeed / (WSXOR && ADXOR ? 1.4142 : 1)
|
||||
const finalSpeed = this.moveSpeed / (WSXOR && ADXOR ? 1.4142 : 1)
|
||||
/* eslint-disable no-nested-ternary */
|
||||
this.viewport.translateBy(
|
||||
(ADXOR ? (actions.moveLeft.pressed ? 1 : -1) : 0) * finalSpeed,
|
||||
(WSXOR ? (actions.moveUp.pressed ? 1 : -1) : 0) * finalSpeed
|
||||
(ADXOR ? (isActionActive('moveLeft') ? 1 : -1) : 0) * finalSpeed,
|
||||
(WSXOR ? (isActionActive('moveUp') ? 1 : -1) : 0) * finalSpeed
|
||||
)
|
||||
/* eslint-enable no-nested-ternary */
|
||||
this.applyViewportTransform()
|
||||
@ -230,14 +235,14 @@ class BlueprintContainer extends PIXI.Container {
|
||||
// Instead of decreasing the global interactionFrequency, call the over and out entity events here
|
||||
this.updateHoverContainer()
|
||||
|
||||
if (actions.build.pressed) {
|
||||
actions.build.call()
|
||||
if (isActionActive('build')) {
|
||||
callAction('build')
|
||||
}
|
||||
if (actions.mine.pressed) {
|
||||
actions.mine.call()
|
||||
if (isActionActive('mine')) {
|
||||
callAction('mine')
|
||||
}
|
||||
if (actions.pasteEntitySettings.pressed) {
|
||||
actions.pasteEntitySettings.call()
|
||||
if (isActionActive('pasteEntitySettings')) {
|
||||
callAction('pasteEntitySettings')
|
||||
}
|
||||
})
|
||||
|
||||
@ -473,7 +478,25 @@ class BlueprintContainer extends PIXI.Container {
|
||||
}
|
||||
}
|
||||
|
||||
public generateGrid(pattern: 'checker' | 'grid' = 'checker'): void {
|
||||
public get gridColor(): number {
|
||||
return this._gridColor
|
||||
}
|
||||
|
||||
public set gridColor(color: number) {
|
||||
this._gridColor = color
|
||||
this.grid.tint = color
|
||||
}
|
||||
|
||||
public get gridPattern(): GridPattern {
|
||||
return this._gridPattern
|
||||
}
|
||||
|
||||
public set gridPattern(pattern: GridPattern) {
|
||||
this._gridPattern = pattern
|
||||
this.generateGrid()
|
||||
}
|
||||
|
||||
private generateGrid(pattern = this.gridPattern): void {
|
||||
const gridGraphics =
|
||||
pattern === 'checker'
|
||||
? new PIXI.Graphics()
|
||||
@ -504,7 +527,7 @@ class BlueprintContainer extends PIXI.Container {
|
||||
const grid = new PIXI.TilingSprite(renderTexture, this.size.x, this.size.y)
|
||||
grid.anchor.set(this.anchor.x, this.anchor.y)
|
||||
|
||||
G.colors.addSpriteForAutomaticTintChange(grid)
|
||||
grid.tint = this.gridColor
|
||||
|
||||
if (this.grid) {
|
||||
const index = this.getChildIndex(this.grid)
|
||||
@ -683,6 +706,22 @@ class BlueprintContainer extends PIXI.Container {
|
||||
return new PIXI.Rectangle(X, Y, W, H)
|
||||
}
|
||||
|
||||
public getPicture(): Promise<Blob> {
|
||||
// getLocalBounds is needed because it seems that it has sideeffects
|
||||
// without it generateTexture returns an empty texture
|
||||
this.getLocalBounds()
|
||||
const region = this.getBlueprintBounds()
|
||||
const texture = G.app.renderer.generateTexture(this, PIXI.SCALE_MODES.LINEAR, 1, region)
|
||||
const canvas = G.app.renderer.plugins.extract.canvas(texture)
|
||||
|
||||
return new Promise(resolve => {
|
||||
canvas.toBlob(blob => {
|
||||
canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height)
|
||||
resolve(blob)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public spawnPaintContainer(itemNameOrEntities: string | Entity[], direction = 0): void {
|
||||
if (this.mode === EditorMode.PAINT) {
|
||||
this.paintContainer.destroy()
|
||||
@ -722,4 +761,4 @@ class BlueprintContainer extends PIXI.Container {
|
||||
}
|
||||
}
|
||||
|
||||
export { EditorMode, BlueprintContainer }
|
||||
export { EditorMode, BlueprintContainer, GridPattern }
|
||||
|
@ -295,7 +295,7 @@ export class EntityContainer {
|
||||
this.entitySprites = []
|
||||
for (const s of EntitySprite.getParts(
|
||||
this.m_Entity,
|
||||
G.quality.hr,
|
||||
|
||||
ignoreConnections ? undefined : G.bp.entityPositionGrid
|
||||
)) {
|
||||
s.setPosition(this.position)
|
||||
|
@ -23,7 +23,7 @@ class BlueprintEntityPaintContainer {
|
||||
|
||||
this.visualizationArea = G.BPC.visualizationAreaContainer.create(this.entity.name, this.position)
|
||||
|
||||
this.entitySprites = EntitySprite.getParts(this.entity, G.quality.hr, this.bp.entityPositionGrid)
|
||||
this.entitySprites = EntitySprite.getParts(this.entity, this.bp.entityPositionGrid)
|
||||
}
|
||||
|
||||
private get entityPosition(): IPoint {
|
||||
|
@ -129,14 +129,11 @@ export class EntityPaintContainer extends PaintContainer {
|
||||
protected redraw(): void {
|
||||
this.removeChildren()
|
||||
this.addChild(
|
||||
...EntitySprite.getParts(
|
||||
{
|
||||
name: this.name,
|
||||
direction: this.directionType === 'input' ? this.direction : (this.direction + 4) % 8,
|
||||
directionType: this.directionType
|
||||
},
|
||||
G.quality.hr
|
||||
)
|
||||
...EntitySprite.getParts({
|
||||
name: this.name,
|
||||
direction: this.directionType === 'input' ? this.direction : (this.direction + 4) % 8,
|
||||
directionType: this.directionType
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -31,9 +31,9 @@ export class EntitySprite extends PIXI.Sprite {
|
||||
return this.nextID
|
||||
}
|
||||
|
||||
public static getParts(entity: IEntityData | Entity, hr: boolean, positionGrid?: PositionGrid): EntitySprite[] {
|
||||
public static getParts(entity: IEntityData | Entity, positionGrid?: PositionGrid): EntitySprite[] {
|
||||
const anims = spriteDataBuilder.getSpriteData({
|
||||
hr,
|
||||
hr: G.hr,
|
||||
dir:
|
||||
positionGrid && entity.type === 'electric_pole' && entity instanceof Entity
|
||||
? G.BPC.wiresContainer.getPowerPoleDirection(entity)
|
||||
|
@ -10,7 +10,16 @@ import generators, { IVisualization } from './generators'
|
||||
import History from './history'
|
||||
import Tile from './tile'
|
||||
|
||||
const oilOutpostSettings = {
|
||||
interface IOilOutpostSettings extends Record<string, string | boolean | number> {
|
||||
DEBUG: boolean
|
||||
PUMPJACK_MODULE: string
|
||||
MIN_GAP_BETWEEN_UNDERGROUNDS: number
|
||||
BEACONS: boolean
|
||||
MIN_AFFECTED_ENTITIES: number
|
||||
BEACON_MODULE: string
|
||||
}
|
||||
|
||||
const oilOutpostSettings: IOilOutpostSettings = {
|
||||
DEBUG: false,
|
||||
PUMPJACK_MODULE: 'productivity_module_3',
|
||||
MIN_GAP_BETWEEN_UNDERGROUNDS: 1,
|
||||
@ -19,6 +28,14 @@ const oilOutpostSettings = {
|
||||
BEACON_MODULE: 'speed_module_3'
|
||||
}
|
||||
|
||||
// this is how it works in factorio but js doesn't support 64bit bitwise operations
|
||||
// uint64_t(developerVersion) |
|
||||
// (uint64_t(minorVersion) << 16) |
|
||||
// (uint64_t(majorVersion) << 32) |
|
||||
// (uint64_t(mainVersion) << 48)
|
||||
const getFactorioVersion = (main = 0, major = 17, minor = 14): number =>
|
||||
(minor << 16) + (major | (main << 16)) * 0xffffffff
|
||||
|
||||
class OurMap<K, V> extends Map<K, V> {
|
||||
public constructor(values?: V[], mapFn?: (value: V) => K) {
|
||||
if (values) {
|
||||
@ -566,10 +583,10 @@ export default class Blueprint extends EventEmitter {
|
||||
entities: this.entities.isEmpty() ? undefined : entityInfo,
|
||||
tiles: this.tiles.isEmpty() ? undefined : tileInfo,
|
||||
item: 'blueprint',
|
||||
version: G.getFactorioVersion(),
|
||||
version: getFactorioVersion(),
|
||||
label: this.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { oilOutpostSettings }
|
||||
export { oilOutpostSettings, getFactorioVersion, IOilOutpostSettings }
|
||||
|
@ -1,5 +1,4 @@
|
||||
import G from '../common/globals'
|
||||
import Blueprint from './blueprint'
|
||||
import Blueprint, { getFactorioVersion } from './blueprint'
|
||||
|
||||
export class Book {
|
||||
private _activeIndex: number
|
||||
@ -56,7 +55,7 @@ export class Book {
|
||||
blueprints,
|
||||
item: 'blueprint_book',
|
||||
active_index: this._activeIndex,
|
||||
version: G.getFactorioVersion()
|
||||
version: getFactorioVersion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,8 +120,10 @@ function addToShift(shift: IPoint | number[], tab: FD.SpriteData): FD.SpriteData
|
||||
}
|
||||
|
||||
function setProperty(img: FD.SpriteData, key: string, val: any): FD.SpriteData {
|
||||
// @ts-ignore
|
||||
img[key] = val
|
||||
if (img.hr_version) {
|
||||
// @ts-ignore
|
||||
img.hr_version[key] = val
|
||||
}
|
||||
return img
|
||||
@ -129,8 +131,10 @@ function setProperty(img: FD.SpriteData, key: string, val: any): FD.SpriteData {
|
||||
|
||||
function setPropertyUsing(img: FD.SpriteData, key: string, key2: string, mult = 1): FD.SpriteData {
|
||||
if (key2) {
|
||||
// @ts-ignore
|
||||
img[key] = img[key2] * mult
|
||||
if (img.hr_version) {
|
||||
// @ts-ignore
|
||||
img.hr_version[key] = img.hr_version[key2] * mult
|
||||
}
|
||||
}
|
||||
|
30
packages/editor/src/global.d.ts
vendored
30
packages/editor/src/global.d.ts
vendored
@ -3,32 +3,6 @@ declare module '*.png' {
|
||||
export default path
|
||||
}
|
||||
|
||||
declare module '*.json' {
|
||||
const content: any
|
||||
export default content
|
||||
}
|
||||
|
||||
interface Navigator {
|
||||
clipboard: {
|
||||
writeText?: (s: string) => Promise<void>
|
||||
readText?: () => Promise<string>
|
||||
}
|
||||
}
|
||||
|
||||
interface Window {
|
||||
doorbellOptions: {
|
||||
tags?: string
|
||||
id: string
|
||||
appKey: string
|
||||
windowLoaded?: boolean
|
||||
onShow?: () => void
|
||||
onHide?: () => void
|
||||
onInitialized?: () => void
|
||||
}
|
||||
}
|
||||
|
||||
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
|
||||
|
||||
interface IPoint {
|
||||
x: number
|
||||
y: number
|
||||
@ -94,7 +68,7 @@ interface ISpriteData {
|
||||
}
|
||||
|
||||
/** Namespace for blueprint string interfaces */
|
||||
namespace BPS {
|
||||
declare namespace BPS {
|
||||
interface IColor {
|
||||
r: number
|
||||
g: number
|
||||
@ -133,7 +107,7 @@ namespace BPS {
|
||||
copper?: IWireColor[]
|
||||
}
|
||||
|
||||
interface IConnection extends Record<string, ConnSide | IWireColor[]> {
|
||||
interface IConnection extends Record<string, IConnSide | IWireColor[]> {
|
||||
1?: IConnSide
|
||||
2?: IConnSide
|
||||
Cu0?: IWireColor[]
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,554 +1,394 @@
|
||||
import * as PIXI from 'pixi.js'
|
||||
|
||||
import FileSaver from 'file-saver'
|
||||
import G from './common/globals'
|
||||
import U from './common/util'
|
||||
|
||||
import { Book } from './factorio-data/book'
|
||||
import Entity from './factorio-data/entity'
|
||||
import Blueprint, { oilOutpostSettings, IOilOutpostSettings } from './factorio-data/blueprint'
|
||||
import bpString, { ModdedBlueprintError, TrainBlueprintError } from './factorio-data/bpString'
|
||||
|
||||
import G from './common/globals'
|
||||
import { TilePaintContainer } from './containers/paintTile'
|
||||
import { BlueprintContainer, EditorMode } from './containers/blueprint'
|
||||
import Blueprint from './factorio-data/blueprint'
|
||||
import initDoorbell from './doorbell'
|
||||
import actions from './actions'
|
||||
import initDatGui from './datgui'
|
||||
import initToasts from './toasts'
|
||||
import spritesheetsLoader from './spritesheetsLoader'
|
||||
import Entity from './factorio-data/entity'
|
||||
import Dialog from './UI/controls/dialog'
|
||||
import { EntityContainer } from './containers/entity'
|
||||
import U from './common/util'
|
||||
import { TilePaintContainer } from './containers/paintTile'
|
||||
import { BlueprintContainer, EditorMode, GridPattern } from './containers/blueprint'
|
||||
|
||||
import UIContainer from './UI/ui'
|
||||
import Dialog from './UI/controls/dialog'
|
||||
|
||||
if (PIXI.utils.isMobile.any) {
|
||||
document.getElementById('loadingScreen').classList.add('mobileError')
|
||||
throw new Error('MOBILE DEVICE DETECTED')
|
||||
}
|
||||
import {
|
||||
initActions,
|
||||
registerAction,
|
||||
callAction,
|
||||
forEachAction,
|
||||
resetKeybinds,
|
||||
importKeybinds,
|
||||
exportKeybinds
|
||||
} from './actions'
|
||||
import spritesheetsLoader from './spritesheetsLoader'
|
||||
|
||||
console.log(
|
||||
'\n%cLooking for the source?\nhttps://github.com/Teoxoy/factorio-blueprint-editor\n',
|
||||
'color: #1f79aa; font-weight: bold'
|
||||
)
|
||||
function initEditor(canvas: HTMLCanvasElement): Promise<void[]> {
|
||||
PIXI.settings.MIPMAP_TEXTURES = PIXI.MIPMAP_MODES.ON
|
||||
PIXI.settings.ROUND_PIXELS = true
|
||||
PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.LINEAR
|
||||
PIXI.settings.WRAP_MODE = PIXI.WRAP_MODES.REPEAT
|
||||
PIXI.settings.RENDER_OPTIONS.antialias = true // for wires
|
||||
PIXI.settings.RENDER_OPTIONS.resolution = window.devicePixelRatio
|
||||
PIXI.settings.RENDER_OPTIONS.autoDensity = true
|
||||
PIXI.GRAPHICS_CURVES.adaptive = true
|
||||
PIXI.settings.FAIL_IF_MAJOR_PERFORMANCE_CAVEAT = false
|
||||
PIXI.settings.ANISOTROPIC_LEVEL = 16
|
||||
// PIXI.settings.PREFER_ENV = 1
|
||||
// PIXI.settings.PRECISION_VERTEX = PIXI.PRECISION.HIGH
|
||||
// PIXI.settings.PRECISION_FRAGMENT = PIXI.PRECISION.HIGH
|
||||
|
||||
const params = window.location.search.slice(1).split('&')
|
||||
G.app = new PIXI.Application({ view: canvas })
|
||||
|
||||
let bpSource: string
|
||||
let bpIndex = 0
|
||||
for (const p of params) {
|
||||
if (p.includes('source')) {
|
||||
bpSource = p.split('=')[1]
|
||||
}
|
||||
if (p.includes('index')) {
|
||||
bpIndex = Number(p.split('=')[1])
|
||||
}
|
||||
}
|
||||
// https://github.com/pixijs/pixi.js/issues/3928
|
||||
// G.app.renderer.plugins.interaction.moveWhenInside = true
|
||||
// G.app.renderer.plugins.interaction.interactionFrequency = 1
|
||||
|
||||
const { guiBPIndex } = initDatGui()
|
||||
initDoorbell()
|
||||
G.app.renderer.resize(window.innerWidth, window.innerHeight)
|
||||
window.addEventListener('resize', () => G.app.renderer.resize(window.innerWidth, window.innerHeight), false)
|
||||
|
||||
const createToast = initToasts()
|
||||
function createErrorMessage(text: string, error: unknown): void {
|
||||
console.error(error)
|
||||
createToast({
|
||||
text:
|
||||
`${text}<br>` +
|
||||
'Please check out the console (F12) for an error message and ' +
|
||||
'report this bug on github or using the feedback button.',
|
||||
type: 'error',
|
||||
timeout: 10000
|
||||
G.BPC = new BlueprintContainer()
|
||||
G.app.stage.addChild(G.BPC)
|
||||
|
||||
G.UI = new UIContainer()
|
||||
G.app.stage.addChild(G.UI)
|
||||
G.UI.showDebuggingLayer = G.debug
|
||||
|
||||
initActions(canvas)
|
||||
registerActions()
|
||||
|
||||
window.addEventListener('unload', () => {
|
||||
G.app.stop()
|
||||
G.app.renderer.textureGC.unload(G.app.stage)
|
||||
G.app.destroy()
|
||||
})
|
||||
}
|
||||
function createBPImportError(error: Error | ModdedBlueprintError): void {
|
||||
if (error instanceof TrainBlueprintError) {
|
||||
createErrorMessage(
|
||||
'Blueprint with train entities not supported yet. If you think this is a mistake:',
|
||||
error.errors
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (error instanceof ModdedBlueprintError) {
|
||||
createErrorMessage(
|
||||
'Blueprint with modded items not supported yet. If you think this is a mistake:',
|
||||
error.errors
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
createErrorMessage('Blueprint string could not be loaded.', error)
|
||||
}
|
||||
function createWelcomeMessage(): void {
|
||||
const notFirstRun = localStorage.getItem('firstRun') === 'false'
|
||||
if (notFirstRun) {
|
||||
return
|
||||
}
|
||||
localStorage.setItem('firstRun', 'false')
|
||||
|
||||
// Wait a bit just to capture the users attention
|
||||
// This way they will see the toast animation
|
||||
setTimeout(() => {
|
||||
createToast({
|
||||
text:
|
||||
'> To access the inventory and start building press E<br>' +
|
||||
'> To import/export a blueprint string use ctrl/cmd + C/V<br>' +
|
||||
'> For more info press I<br>' +
|
||||
'> Also check out the settings area',
|
||||
timeout: 30000
|
||||
})
|
||||
}, 1000)
|
||||
return Promise.all(
|
||||
// Load spritesheets
|
||||
spritesheetsLoader.getAllPromises()
|
||||
)
|
||||
}
|
||||
|
||||
PIXI.settings.MIPMAP_TEXTURES = PIXI.MIPMAP_MODES.ON
|
||||
PIXI.settings.ROUND_PIXELS = true
|
||||
PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.LINEAR
|
||||
PIXI.settings.WRAP_MODE = PIXI.WRAP_MODES.REPEAT
|
||||
PIXI.settings.RENDER_OPTIONS.antialias = true // for wires
|
||||
PIXI.settings.RENDER_OPTIONS.resolution = window.devicePixelRatio
|
||||
PIXI.settings.RENDER_OPTIONS.autoDensity = true
|
||||
PIXI.GRAPHICS_CURVES.adaptive = true
|
||||
PIXI.settings.FAIL_IF_MAJOR_PERFORMANCE_CAVEAT = false
|
||||
PIXI.settings.ANISOTROPIC_LEVEL = 16
|
||||
// PIXI.settings.PREFER_ENV = 1
|
||||
// PIXI.settings.PRECISION_VERTEX = PIXI.PRECISION.HIGH
|
||||
// PIXI.settings.PRECISION_FRAGMENT = PIXI.PRECISION.HIGH
|
||||
function loadBlueprint(bp: Blueprint): void {
|
||||
G.bp = bp
|
||||
|
||||
G.app = new PIXI.Application({ view: document.getElementById('editor') as HTMLCanvasElement })
|
||||
|
||||
// https://github.com/pixijs/pixi.js/issues/3928
|
||||
// G.app.renderer.plugins.interaction.moveWhenInside = true
|
||||
// G.app.renderer.plugins.interaction.interactionFrequency = 1
|
||||
|
||||
G.app.renderer.resize(window.innerWidth, window.innerHeight)
|
||||
window.addEventListener(
|
||||
'resize',
|
||||
() => {
|
||||
G.app.renderer.resize(window.innerWidth, window.innerHeight)
|
||||
},
|
||||
false
|
||||
)
|
||||
|
||||
G.BPC = new BlueprintContainer()
|
||||
G.app.stage.addChild(G.BPC)
|
||||
|
||||
G.UI = new UIContainer()
|
||||
G.app.stage.addChild(G.UI)
|
||||
G.UI.showDebuggingLayer = G.debug
|
||||
|
||||
Promise.all([
|
||||
// Get bp from source
|
||||
// catch the error here so that Promise.all can resolve
|
||||
bpString.getBlueprintOrBookFromSource(bpSource).catch(error => {
|
||||
createBPImportError(error)
|
||||
return new Blueprint()
|
||||
}),
|
||||
// Wait for fonts to get loaded
|
||||
document.fonts.ready,
|
||||
// Load spritesheets
|
||||
...spritesheetsLoader.getAllPromises()
|
||||
])
|
||||
.then(data => {
|
||||
// Load quickbarItemNames from localStorage
|
||||
if (localStorage.getItem('quickbarItemNames')) {
|
||||
const quickbarItemNames = JSON.parse(localStorage.getItem('quickbarItemNames'))
|
||||
G.UI.quickbarContainer.generateSlots(quickbarItemNames)
|
||||
}
|
||||
|
||||
loadBp(data[0], false)
|
||||
|
||||
createWelcomeMessage()
|
||||
})
|
||||
.catch(error => createErrorMessage('Something went wrong.', error))
|
||||
|
||||
function loadBp(bpOrBook: Blueprint | Book, clearData = true): void {
|
||||
if (bpOrBook instanceof Book) {
|
||||
G.book = bpOrBook
|
||||
G.bp = G.book.getBlueprint(bpIndex ? bpIndex : undefined)
|
||||
|
||||
guiBPIndex.max(G.book.lastBookIndex).setValue(G.book.activeIndex)
|
||||
} else {
|
||||
G.book = undefined
|
||||
G.bp = bpOrBook
|
||||
|
||||
guiBPIndex.setValue(0).max(0)
|
||||
}
|
||||
|
||||
if (clearData) {
|
||||
G.BPC.clearData()
|
||||
}
|
||||
G.BPC.clearData()
|
||||
G.BPC.initBP()
|
||||
G.loadingScreen.hide()
|
||||
|
||||
if (!(bpOrBook instanceof Blueprint && bpOrBook.isEmpty())) {
|
||||
createToast({ text: 'Blueprint string loaded successfully', type: 'success' })
|
||||
}
|
||||
|
||||
Dialog.closeAll()
|
||||
}
|
||||
|
||||
// If the tab is not active then stop the app
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
G.app.start()
|
||||
} else {
|
||||
G.app.stop()
|
||||
}
|
||||
})
|
||||
function registerActions(): void {
|
||||
registerAction('moveUp', 'w')
|
||||
registerAction('moveLeft', 'a')
|
||||
registerAction('moveDown', 's')
|
||||
registerAction('moveRight', 'd')
|
||||
|
||||
window.addEventListener('unload', () => {
|
||||
G.app.stop()
|
||||
G.app.renderer.textureGC.unload(G.app.stage)
|
||||
G.app.destroy()
|
||||
})
|
||||
registerAction('showInfo', 'alt').bind({
|
||||
press: () => G.BPC.overlayContainer.toggleEntityInfoVisibility()
|
||||
})
|
||||
|
||||
// ACTIONS //
|
||||
|
||||
actions.importKeybinds(JSON.parse(localStorage.getItem('keybinds')))
|
||||
|
||||
window.addEventListener('unload', () => {
|
||||
const keybinds = actions.exportKeybinds()
|
||||
if (Object.keys(keybinds).length) {
|
||||
localStorage.setItem('keybinds', JSON.stringify(keybinds))
|
||||
} else {
|
||||
localStorage.removeItem('keybinds')
|
||||
}
|
||||
})
|
||||
|
||||
actions.copyBPString.bind({
|
||||
press: e => {
|
||||
if (G.bp.isEmpty()) {
|
||||
return
|
||||
registerAction('closeWindow', 'esc').bind({
|
||||
press: () => {
|
||||
Dialog.closeLast()
|
||||
}
|
||||
})
|
||||
|
||||
const onSuccess = (): void => {
|
||||
createToast({ text: 'Blueprint string copied to clipboard', type: 'success' })
|
||||
}
|
||||
|
||||
const onError = (error: Error): void => {
|
||||
createErrorMessage('Blueprint string could not be generated.', error)
|
||||
}
|
||||
|
||||
const bpOrBook = G.book ? G.book : G.bp
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
bpString
|
||||
.encode(bpOrBook)
|
||||
.then(s => navigator.clipboard.writeText(s))
|
||||
.then(onSuccess)
|
||||
.catch(onError)
|
||||
} else {
|
||||
const data = bpString.encodeSync(bpOrBook)
|
||||
if (data.value) {
|
||||
e.clipboardData.setData('text/plain', data.value)
|
||||
onSuccess()
|
||||
registerAction('inventory', 'e').bind({
|
||||
press: () => {
|
||||
// If there is a dialog open, assume user wants to close it
|
||||
if (Dialog.anyOpen()) {
|
||||
Dialog.closeLast()
|
||||
} else {
|
||||
onError(data.error)
|
||||
G.UI.createInventory('Inventory', undefined, G.BPC.spawnPaintContainer.bind(G.BPC))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
actions.pasteBPString.bind({
|
||||
press: e => {
|
||||
G.loadingScreen.show()
|
||||
registerAction('focus', 'f').bind({ press: () => G.BPC.centerViewport() })
|
||||
|
||||
const promise =
|
||||
navigator.clipboard && navigator.clipboard.readText
|
||||
? navigator.clipboard.readText()
|
||||
: Promise.resolve(e.clipboardData.getData('text'))
|
||||
|
||||
promise
|
||||
.then(bpString.getBlueprintOrBookFromSource)
|
||||
.then(loadBp)
|
||||
.catch(error => {
|
||||
G.loadingScreen.hide()
|
||||
createBPImportError(error)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
actions.clear.bind({
|
||||
press: () => {
|
||||
loadBp(new Blueprint())
|
||||
}
|
||||
})
|
||||
|
||||
actions.takePicture.bind({
|
||||
press: () => {
|
||||
if (G.bp.isEmpty()) {
|
||||
return
|
||||
registerAction('rotate', 'r').bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
G.BPC.hoverContainer.entity.rotate(false, true)
|
||||
} else if (G.BPC.mode === EditorMode.PAINT) {
|
||||
G.BPC.paintContainer.rotate()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// getLocalBounds is needed because it seems that it has sideeffects
|
||||
// without it generateTexture returns an empty texture
|
||||
G.BPC.getLocalBounds()
|
||||
const region = G.BPC.getBlueprintBounds()
|
||||
const texture = G.app.renderer.generateTexture(G.BPC, PIXI.SCALE_MODES.LINEAR, 1, region)
|
||||
const canvas = G.app.renderer.plugins.extract.canvas(texture)
|
||||
registerAction('reverseRotate', 'shift+r').bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
G.BPC.hoverContainer.entity.rotate(true, true)
|
||||
} else if (G.BPC.mode === EditorMode.PAINT) {
|
||||
G.BPC.paintContainer.rotate(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
canvas.toBlob(blob => {
|
||||
FileSaver.saveAs(blob, `${G.bp.name}.png`)
|
||||
createToast({ text: 'Blueprint image successfully generated', type: 'success' })
|
||||
registerAction('pipette', 'q').bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
const entity = G.BPC.hoverContainer.entity
|
||||
const itemName = Entity.getItemName(entity.name)
|
||||
const direction = entity.directionType === 'output' ? (entity.direction + 4) % 8 : entity.direction
|
||||
G.BPC.spawnPaintContainer(itemName, direction)
|
||||
} else if (G.BPC.mode === EditorMode.PAINT) {
|
||||
G.BPC.paintContainer.destroy()
|
||||
}
|
||||
G.BPC.exitCopyMode(true)
|
||||
G.BPC.exitDeleteMode(true)
|
||||
}
|
||||
})
|
||||
|
||||
// Clear
|
||||
canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height)
|
||||
registerAction('increaseTileBuildingArea', ']').bind({
|
||||
press: () => {
|
||||
if (G.BPC.paintContainer instanceof TilePaintContainer) {
|
||||
G.BPC.paintContainer.increaseSize()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registerAction('decreaseTileBuildingArea', '[').bind({
|
||||
press: () => {
|
||||
if (G.BPC.paintContainer instanceof TilePaintContainer) {
|
||||
G.BPC.paintContainer.decreaseSize()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registerAction('undo', 'modifier+z').bind({
|
||||
press: () => {
|
||||
G.bp.history.undo()
|
||||
},
|
||||
repeat: true
|
||||
})
|
||||
|
||||
registerAction('redo', 'modifier+y').bind({
|
||||
press: () => {
|
||||
G.bp.history.redo()
|
||||
},
|
||||
repeat: true
|
||||
})
|
||||
|
||||
registerAction('copySelection', 'modifier+lclick').bind({
|
||||
press: () => G.BPC.enterCopyMode(),
|
||||
release: () => G.BPC.exitCopyMode()
|
||||
})
|
||||
registerAction('deleteSelection', 'modifier+rclick').bind({
|
||||
press: () => G.BPC.enterDeleteMode(),
|
||||
release: () => G.BPC.exitDeleteMode()
|
||||
})
|
||||
|
||||
registerAction('pan', 'lclick').bind({
|
||||
press: () => G.BPC.enterPanMode(),
|
||||
release: () => G.BPC.exitPanMode()
|
||||
})
|
||||
|
||||
registerAction('zoomIn', 'wheelNeg').bind({
|
||||
press: () => {
|
||||
G.BPC.zoom(true)
|
||||
}
|
||||
})
|
||||
|
||||
registerAction('zoomOut', 'wheelPos').bind({
|
||||
press: () => {
|
||||
G.BPC.zoom(false)
|
||||
}
|
||||
})
|
||||
|
||||
registerAction('build', 'lclick').bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.PAINT) {
|
||||
G.BPC.paintContainer.placeEntityContainer()
|
||||
}
|
||||
},
|
||||
repeat: true
|
||||
})
|
||||
|
||||
registerAction('mine', 'rclick').bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
G.bp.removeEntity(G.BPC.hoverContainer.entity)
|
||||
}
|
||||
if (G.BPC.mode === EditorMode.PAINT) {
|
||||
G.BPC.paintContainer.removeContainerUnder()
|
||||
}
|
||||
},
|
||||
repeat: true
|
||||
})
|
||||
|
||||
registerAction('moveEntityUp', 'up').bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
G.BPC.hoverContainer.entity.moveBy({ x: 0, y: -1 })
|
||||
}
|
||||
}
|
||||
})
|
||||
registerAction('moveEntityLeft', 'left').bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
G.BPC.hoverContainer.entity.moveBy({ x: -1, y: 0 })
|
||||
}
|
||||
}
|
||||
})
|
||||
registerAction('moveEntityDown', 'down').bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
G.BPC.hoverContainer.entity.moveBy({ x: 0, y: 1 })
|
||||
}
|
||||
}
|
||||
})
|
||||
registerAction('moveEntityRight', 'right').bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
G.BPC.hoverContainer.entity.moveBy({ x: 1, y: 0 })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registerAction('openEntityGUI', 'lclick').bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
if (G.debug) {
|
||||
console.log(G.BPC.hoverContainer.entity.serialize())
|
||||
}
|
||||
|
||||
Dialog.closeAll()
|
||||
G.UI.createEditor(G.BPC.hoverContainer.entity)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let entityForCopyData: Entity | undefined
|
||||
const copyEntitySettingsAction = registerAction('copyEntitySettings', 'shift+rclick')
|
||||
copyEntitySettingsAction.bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
// Store reference to source entity
|
||||
entityForCopyData = G.BPC.hoverContainer.entity
|
||||
}
|
||||
}
|
||||
})
|
||||
registerAction('pasteEntitySettings', 'shift+lclick').bind({
|
||||
press: () => {
|
||||
if (entityForCopyData && G.BPC.mode === EditorMode.EDIT) {
|
||||
// Hand over reference of source entity to target entity for pasting data
|
||||
G.BPC.hoverContainer.entity.pasteSettings(entityForCopyData)
|
||||
}
|
||||
},
|
||||
repeat: true
|
||||
})
|
||||
// TODO: Move this somewhere else - I don't think this is the right place for it
|
||||
{
|
||||
let copyCursorBox: PIXI.Container | undefined
|
||||
const deferred = new U.Deferred()
|
||||
const createCopyCursorBox = (): void => {
|
||||
if (
|
||||
copyCursorBox === undefined &&
|
||||
G.BPC.mode === EditorMode.EDIT &&
|
||||
entityForCopyData &&
|
||||
EntityContainer.mappings.has(entityForCopyData.entityNumber) &&
|
||||
G.BPC.hoverContainer.entity.canPasteSettings(entityForCopyData)
|
||||
) {
|
||||
const srcEnt = EntityContainer.mappings.get(entityForCopyData.entityNumber)
|
||||
copyCursorBox = G.BPC.overlayContainer.createCursorBox(srcEnt.position, entityForCopyData.size, 'copy')
|
||||
Promise.race([
|
||||
deferred.promise,
|
||||
new Promise(resolve => copyEntitySettingsAction.bind({ press: resolve, once: true })),
|
||||
new Promise(resolve => G.BPC.once('removeHoverContainer', resolve))
|
||||
]).then(() => {
|
||||
deferred.reset()
|
||||
copyCursorBox.destroy()
|
||||
copyCursorBox = undefined
|
||||
})
|
||||
}
|
||||
}
|
||||
const action = registerAction('tryPasteEntitySettings', 'shift')
|
||||
action.bind({ press: createCopyCursorBox, release: () => deferred.resolve() })
|
||||
G.BPC.on('createHoverContainer', () => {
|
||||
if (action.pressed) {
|
||||
createCopyCursorBox()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
actions.showInfo.bind({
|
||||
press: () => G.BPC.overlayContainer.toggleEntityInfoVisibility()
|
||||
})
|
||||
registerAction('quickbar1', '1').bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(0) })
|
||||
registerAction('quickbar2', '2').bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(1) })
|
||||
registerAction('quickbar3', '3').bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(2) })
|
||||
registerAction('quickbar4', '4').bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(3) })
|
||||
registerAction('quickbar5', '5').bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(4) })
|
||||
registerAction('quickbar6', 'shift+1').bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(5) })
|
||||
registerAction('quickbar7', 'shift+2').bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(6) })
|
||||
registerAction('quickbar8', 'shift+3').bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(7) })
|
||||
registerAction('quickbar9', 'shift+4').bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(8) })
|
||||
registerAction('quickbar10', 'shift+5').bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(9) })
|
||||
registerAction('changeActiveQuickbar', 'x').bind({ press: () => G.UI.quickbarContainer.changeActiveQuickbar() })
|
||||
}
|
||||
|
||||
actions.info.bind({
|
||||
press: () => {
|
||||
const infoPanel = document.getElementById('info-panel')
|
||||
if (infoPanel.classList.contains('active')) {
|
||||
infoPanel.classList.remove('active')
|
||||
} else {
|
||||
infoPanel.classList.add('active')
|
||||
}
|
||||
}
|
||||
})
|
||||
const getPicture = (): Promise<Blob> => G.BPC.getPicture()
|
||||
|
||||
actions.closeWindow.bind({
|
||||
press: () => {
|
||||
Dialog.closeLast()
|
||||
}
|
||||
})
|
||||
const getMoveSpeed = (): number => G.BPC.moveSpeed
|
||||
const setMoveSpeed = (speed: number): void => {
|
||||
G.BPC.moveSpeed = speed
|
||||
}
|
||||
const getGridColor = (): number => G.BPC.gridColor
|
||||
const setGridColor = (color: number): void => {
|
||||
G.BPC.gridColor = color
|
||||
}
|
||||
const getGridPattern = (): GridPattern => G.BPC.gridPattern
|
||||
const setGridPattern = (pattern: GridPattern): void => {
|
||||
G.BPC.gridPattern = pattern
|
||||
}
|
||||
|
||||
actions.inventory.bind({
|
||||
press: () => {
|
||||
// If there is a dialog open, assume user wants to close it
|
||||
if (Dialog.anyOpen()) {
|
||||
Dialog.closeLast()
|
||||
} else {
|
||||
G.UI.createInventory('Inventory', undefined, G.BPC.spawnPaintContainer.bind(G.BPC))
|
||||
}
|
||||
}
|
||||
})
|
||||
const getQuickbarItems = (): string[] => G.UI.quickbarContainer.serialize()
|
||||
const setQuickbarItems = (items: string[]): void => {
|
||||
G.UI.quickbarContainer.generateSlots(items)
|
||||
}
|
||||
|
||||
actions.focus.bind({ press: () => G.BPC.centerViewport() })
|
||||
|
||||
actions.rotate.bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
G.BPC.hoverContainer.entity.rotate(false, true)
|
||||
} else if (G.BPC.mode === EditorMode.PAINT) {
|
||||
G.BPC.paintContainer.rotate()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
actions.reverseRotate.bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
G.BPC.hoverContainer.entity.rotate(true, true)
|
||||
} else if (G.BPC.mode === EditorMode.PAINT) {
|
||||
G.BPC.paintContainer.rotate(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
actions.pipette.bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
const entity = G.BPC.hoverContainer.entity
|
||||
const itemName = Entity.getItemName(entity.name)
|
||||
const direction = entity.directionType === 'output' ? (entity.direction + 4) % 8 : entity.direction
|
||||
G.BPC.spawnPaintContainer(itemName, direction)
|
||||
} else if (G.BPC.mode === EditorMode.PAINT) {
|
||||
G.BPC.paintContainer.destroy()
|
||||
}
|
||||
G.BPC.exitCopyMode(true)
|
||||
G.BPC.exitDeleteMode(true)
|
||||
}
|
||||
})
|
||||
|
||||
actions.increaseTileBuildingArea.bind({
|
||||
press: () => {
|
||||
if (G.BPC.paintContainer instanceof TilePaintContainer) {
|
||||
G.BPC.paintContainer.increaseSize()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
actions.decreaseTileBuildingArea.bind({
|
||||
press: () => {
|
||||
if (G.BPC.paintContainer instanceof TilePaintContainer) {
|
||||
G.BPC.paintContainer.decreaseSize()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
actions.undo.bind({
|
||||
press: () => {
|
||||
G.bp.history.undo()
|
||||
},
|
||||
repeat: true
|
||||
})
|
||||
|
||||
actions.redo.bind({
|
||||
press: () => {
|
||||
G.bp.history.redo()
|
||||
},
|
||||
repeat: true
|
||||
})
|
||||
|
||||
actions.generateOilOutpost.bind({
|
||||
press: () => {
|
||||
const errorMessage = G.bp.generatePipes()
|
||||
if (errorMessage) {
|
||||
createToast({ text: errorMessage, type: 'warning' })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
actions.copySelection.bind({
|
||||
press: () => G.BPC.enterCopyMode(),
|
||||
release: () => G.BPC.exitCopyMode()
|
||||
})
|
||||
actions.deleteSelection.bind({
|
||||
press: () => G.BPC.enterDeleteMode(),
|
||||
release: () => G.BPC.exitDeleteMode()
|
||||
})
|
||||
|
||||
actions.pan.bind({
|
||||
press: () => G.BPC.enterPanMode(),
|
||||
release: () => G.BPC.exitPanMode()
|
||||
})
|
||||
|
||||
actions.zoomIn.bind({
|
||||
press: () => {
|
||||
G.BPC.zoom(true)
|
||||
}
|
||||
})
|
||||
|
||||
actions.zoomOut.bind({
|
||||
press: () => {
|
||||
G.BPC.zoom(false)
|
||||
}
|
||||
})
|
||||
|
||||
actions.build.bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.PAINT) {
|
||||
G.BPC.paintContainer.placeEntityContainer()
|
||||
}
|
||||
},
|
||||
repeat: true
|
||||
})
|
||||
|
||||
actions.mine.bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
G.bp.removeEntity(G.BPC.hoverContainer.entity)
|
||||
}
|
||||
if (G.BPC.mode === EditorMode.PAINT) {
|
||||
G.BPC.paintContainer.removeContainerUnder()
|
||||
}
|
||||
},
|
||||
repeat: true
|
||||
})
|
||||
|
||||
actions.moveEntityUp.bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
G.BPC.hoverContainer.entity.moveBy({ x: 0, y: -1 })
|
||||
}
|
||||
}
|
||||
})
|
||||
actions.moveEntityLeft.bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
G.BPC.hoverContainer.entity.moveBy({ x: -1, y: 0 })
|
||||
}
|
||||
}
|
||||
})
|
||||
actions.moveEntityDown.bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
G.BPC.hoverContainer.entity.moveBy({ x: 0, y: 1 })
|
||||
}
|
||||
}
|
||||
})
|
||||
actions.moveEntityRight.bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
G.BPC.hoverContainer.entity.moveBy({ x: 1, y: 0 })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
actions.openEntityGUI.bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
if (G.debug) {
|
||||
console.log(G.BPC.hoverContainer.entity.serialize())
|
||||
}
|
||||
|
||||
Dialog.closeAll()
|
||||
G.UI.createEditor(G.BPC.hoverContainer.entity)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let entityForCopyData: Entity | undefined
|
||||
actions.copyEntitySettings.bind({
|
||||
press: () => {
|
||||
if (G.BPC.mode === EditorMode.EDIT) {
|
||||
// Store reference to source entity
|
||||
entityForCopyData = G.BPC.hoverContainer.entity
|
||||
}
|
||||
}
|
||||
})
|
||||
actions.pasteEntitySettings.bind({
|
||||
press: () => {
|
||||
if (entityForCopyData && G.BPC.mode === EditorMode.EDIT) {
|
||||
// Hand over reference of source entity to target entity for pasting data
|
||||
G.BPC.hoverContainer.entity.pasteSettings(entityForCopyData)
|
||||
}
|
||||
},
|
||||
repeat: true
|
||||
})
|
||||
// TODO: Move this somewhere else - I don't think this is the right place for it
|
||||
{
|
||||
let copyCursorBox: PIXI.Container | undefined
|
||||
const deferred = new U.Deferred()
|
||||
const createCopyCursorBox = (): void => {
|
||||
if (
|
||||
copyCursorBox === undefined &&
|
||||
G.BPC.mode === EditorMode.EDIT &&
|
||||
entityForCopyData &&
|
||||
EntityContainer.mappings.has(entityForCopyData.entityNumber) &&
|
||||
G.BPC.hoverContainer.entity.canPasteSettings(entityForCopyData)
|
||||
) {
|
||||
const srcEnt = EntityContainer.mappings.get(entityForCopyData.entityNumber)
|
||||
copyCursorBox = G.BPC.overlayContainer.createCursorBox(srcEnt.position, entityForCopyData.size, 'copy')
|
||||
Promise.race([
|
||||
deferred.promise,
|
||||
new Promise(resolve => actions.copyEntitySettings.bind({ press: resolve, once: true })),
|
||||
new Promise(resolve => G.BPC.once('removeHoverContainer', resolve))
|
||||
]).then(() => {
|
||||
deferred.reset()
|
||||
copyCursorBox.destroy()
|
||||
copyCursorBox = undefined
|
||||
})
|
||||
}
|
||||
}
|
||||
actions.tryPasteEntitySettings.bind({ press: createCopyCursorBox, release: () => deferred.resolve() })
|
||||
G.BPC.on('createHoverContainer', () => {
|
||||
if (actions.tryPasteEntitySettings.pressed) {
|
||||
createCopyCursorBox()
|
||||
const getOilOutpostSettings = (): IOilOutpostSettings => oilOutpostSettings
|
||||
const setOilOutpostSettings = (settings: IOilOutpostSettings): void => {
|
||||
Object.keys(oilOutpostSettings).forEach(k => {
|
||||
if (settings[k]) {
|
||||
oilOutpostSettings[k] = settings[k]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
actions.quickbar1.bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(0) })
|
||||
actions.quickbar2.bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(1) })
|
||||
actions.quickbar3.bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(2) })
|
||||
actions.quickbar4.bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(3) })
|
||||
actions.quickbar5.bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(4) })
|
||||
actions.quickbar6.bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(5) })
|
||||
actions.quickbar7.bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(6) })
|
||||
actions.quickbar8.bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(7) })
|
||||
actions.quickbar9.bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(8) })
|
||||
actions.quickbar10.bind({ press: () => G.UI.quickbarContainer.bindKeyToSlot(9) })
|
||||
actions.changeActiveQuickbar.bind({ press: () => G.UI.quickbarContainer.changeActiveQuickbar() })
|
||||
const isDebuggingOn = (): boolean => G.debug
|
||||
const setDebugging = (on: boolean): void => {
|
||||
G.debug = on
|
||||
G.UI.showDebuggingLayer = on
|
||||
if (G.bp) {
|
||||
G.bp.history.logging = on
|
||||
}
|
||||
}
|
||||
|
||||
export { Book, Blueprint, ModdedBlueprintError, TrainBlueprintError }
|
||||
export default {
|
||||
initEditor,
|
||||
bpStringEncodeDecode: bpString,
|
||||
loadBlueprint,
|
||||
registerAction,
|
||||
getPicture,
|
||||
getMoveSpeed,
|
||||
setMoveSpeed,
|
||||
getGridColor,
|
||||
setGridColor,
|
||||
getGridPattern,
|
||||
setGridPattern,
|
||||
getQuickbarItems,
|
||||
setQuickbarItems,
|
||||
getOilOutpostSettings,
|
||||
setOilOutpostSettings,
|
||||
isDebuggingOn,
|
||||
setDebugging,
|
||||
callAction,
|
||||
forEachAction,
|
||||
resetKeybinds,
|
||||
importKeybinds,
|
||||
exportKeybinds
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import LRentitySpritesheetPNG from 'factorio-data/data/graphics/LREntitySpritesheet.png'
|
||||
import LRentitySpritesheetCompressedPNG from 'factorio-data/data/graphics/LREntitySpritesheetCompressed.png'
|
||||
import LRentitySpritesheetJSON from 'factorio-data/data/graphics/LREntitySpritesheet.json'
|
||||
import HRentitySpritesheetPNG from 'factorio-data/data/graphics/HREntitySpritesheet.png'
|
||||
import HRentitySpritesheetCompressedPNG from 'factorio-data/data/graphics/HREntitySpritesheetCompressed.png'
|
||||
import HRentitySpritesheetJSON from 'factorio-data/data/graphics/HREntitySpritesheet.json'
|
||||
import iconSpritesheetPNG from 'factorio-data/data/graphics/iconSpritesheet.png'
|
||||
@ -18,47 +16,13 @@ import util from './common/util'
|
||||
function getAllPromises(): Promise<void>[] {
|
||||
return [
|
||||
[
|
||||
/* eslint-disable no-nested-ternary */
|
||||
G.quality.hr
|
||||
? G.quality.compressed
|
||||
? HRentitySpritesheetCompressedPNG
|
||||
: HRentitySpritesheetPNG
|
||||
: G.quality.compressed
|
||||
? LRentitySpritesheetCompressedPNG
|
||||
: LRentitySpritesheetPNG,
|
||||
/* eslint-enable no-nested-ternary */
|
||||
G.quality.hr ? HRentitySpritesheetJSON : LRentitySpritesheetJSON
|
||||
G.hr ? HRentitySpritesheetCompressedPNG : LRentitySpritesheetCompressedPNG,
|
||||
G.hr ? HRentitySpritesheetJSON : LRentitySpritesheetJSON
|
||||
],
|
||||
[iconSpritesheetPNG, iconSpritesheetJSON],
|
||||
[utilitySpritesheetPNG, utilitySpritesheetJSON],
|
||||
[tilesSpritesheetPNG, tilesSpritesheetJSON]
|
||||
].map(data => loadSpritesheet(data[0], data[1]))
|
||||
}
|
||||
|
||||
function changeQuality(hr: boolean, compressed: boolean): void {
|
||||
G.loadingScreen.show()
|
||||
|
||||
G.BPC.clearData()
|
||||
|
||||
Object.keys(PIXI.utils.TextureCache)
|
||||
.filter(texture => texture.includes('graphics/entity/'))
|
||||
.forEach(k => PIXI.utils.TextureCache[k].destroy(true))
|
||||
|
||||
loadSpritesheet(
|
||||
/* eslint-disable no-nested-ternary */
|
||||
hr
|
||||
? compressed
|
||||
? HRentitySpritesheetCompressedPNG
|
||||
: HRentitySpritesheetPNG
|
||||
: compressed
|
||||
? LRentitySpritesheetCompressedPNG
|
||||
: LRentitySpritesheetPNG,
|
||||
/* eslint-enable no-nested-ternary */
|
||||
hr ? HRentitySpritesheetJSON : LRentitySpritesheetJSON
|
||||
).then(() => {
|
||||
G.BPC.initBP()
|
||||
G.loadingScreen.hide()
|
||||
})
|
||||
].map(data => loadSpritesheet(data[0] as string, data[1]))
|
||||
}
|
||||
|
||||
function blobToImageBitmap(blob: Blob): Promise<ImageBitmap | HTMLImageElement> {
|
||||
@ -108,6 +72,5 @@ function loadSpritesheet(src: string, json: any): Promise<void> {
|
||||
}
|
||||
|
||||
export default {
|
||||
getAllPromises,
|
||||
changeQuality
|
||||
getAllPromises
|
||||
}
|
||||
|
18
packages/editor/tsconfig.json
Normal file
18
packages/editor/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
|
||||
"target": "es6",
|
||||
"module": "es6",
|
||||
"moduleResolution": "node",
|
||||
"lib": ["dom", "es6", "es2016.array.include", "es2017.object"],
|
||||
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
}
|
||||
}
|
4
packages/website/.browserslistrc
Normal file
4
packages/website/.browserslistrc
Normal file
@ -0,0 +1,4 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
Firefox ESR
|
||||
not dead
|
2
packages/website/.postcssrc.yml
Normal file
2
packages/website/.postcssrc.yml
Normal file
@ -0,0 +1,2 @@
|
||||
plugins:
|
||||
postcss-preset-env: {}
|
45
packages/website/package.json
Normal file
45
packages/website/package.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@fbe/website",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server",
|
||||
"build": "webpack --mode=production"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fbe/editor": "*",
|
||||
"dat.gui": "^0.7.6",
|
||||
"file-saver": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dat.gui": "^0.7.4",
|
||||
"@types/file-saver": "^2.0.1",
|
||||
"@types/html-webpack-plugin": "^3.2.1",
|
||||
"@types/mini-css-extract-plugin": "^0.8.0",
|
||||
"@types/node": "^12.6.9",
|
||||
"@types/optimize-css-assets-webpack-plugin": "^1.3.4",
|
||||
"@types/terser-webpack-plugin": "^1.2.1",
|
||||
"@types/webpack": "^4.32.1",
|
||||
"@types/webpack-dev-server": "^3.1.7",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"css-loader": "^3.1.0",
|
||||
"extract-loader": "^3.1.0",
|
||||
"file-loader": "^4.1.0",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"mini-css-extract-plugin": "^0.8.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"stylus": "^0.54.5",
|
||||
"stylus-loader": "^3.0.2",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
"terser-webpack-plugin": "^1.4.1",
|
||||
"ts-loader": "^6.0.4",
|
||||
"ts-node": "^8.3.0",
|
||||
"typescript": "^3.5.3",
|
||||
"webpack": "^4.39.1",
|
||||
"webpack-cli": "^3.3.6",
|
||||
"webpack-dev-server": "^3.7.2"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
1
packages/website/src/assets/loadingWheel.svg
Normal file
1
packages/website/src/assets/loadingWheel.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 19 KiB |
1
packages/website/src/assets/logo.svg
Normal file
1
packages/website/src/assets/logo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 487 KiB |
@ -1,4 +1,19 @@
|
||||
export default function initDoorbell(): void {
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/interface-name-prefix
|
||||
interface Window {
|
||||
doorbellOptions: {
|
||||
tags?: string
|
||||
id: string
|
||||
appKey: string
|
||||
windowLoaded?: boolean
|
||||
onShow?: () => void
|
||||
onHide?: () => void
|
||||
onInitialized?: () => void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function initFeedbackButton(): void {
|
||||
window.doorbellOptions = {
|
||||
id: '9657',
|
||||
appKey: 'z1scfSY8hpBNiIFWxBg50tkhjvFKhHMdhfGNMp6YCUZVttoLOqtrlhk4ca9asDCy',
|
94
packages/website/src/index.html
Normal file
94
packages/website/src/index.html
Normal file
@ -0,0 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="initial-scale=1, maximum-scale=1, user-scalable=no, minimum-scale=1, width=device-width, height=device-height"
|
||||
/>
|
||||
<meta
|
||||
name="description"
|
||||
content="A feature-rich Factorio Blueprint Editor. You can now edit your blueprints in the browser!"
|
||||
/>
|
||||
|
||||
<title>Factorio Blueprint Editor</title>
|
||||
|
||||
<link rel="icon" href="assets/favicon.png" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Cookie" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body oncontextmenu="return false;">
|
||||
<div id="loadingScreen" class="active">
|
||||
<p id="mobileError">This application is not compatible with mobile devices!</p>
|
||||
<img id="logo" src="assets/logo.svg" />
|
||||
<img class="loadingWheel" src="assets/loadingWheel.svg" />
|
||||
</div>
|
||||
<div id="corner-panel">
|
||||
<!-- prettier-ignore -->
|
||||
<svg viewBox="0 0 154 60" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.41421"><g fill="#fe7520"><path d="M134.156 58.027h-31.954l-5.021-5.223V18.555l5.021-5.223h31.954l5.02 5.223v6.132h-27.905v6.648h19.68v8.689h-19.68v6.648h27.905v6.132l-5.02 5.223zM15.711 58.027H6.638l-5.021-5.223V18.555l5.021-5.223h31.953l5.021 5.223v6.132H15.706v6.679h19.681v8.69h-19.68l.004 17.971zM85.662 58.027H53.708l-5.02-5.223V18.555l5.02-5.223h23.601l5.02 5.223v6.132h-.003v6.639l8.357 8.737v12.741l-5.021 5.223zM75.266 40.024H62.777v6.648H79.71l.001-2.023-4.445-4.625zm-3.749-15.337h-8.74v6.648h8.74v-6.648zM146.876 46.672l5.406 5.228v6.127h-11.741V46.672h6.335zM49.843 14.755h-7.645l-4.479-4.48 8.302-8.302 8.302 8.302-4.48 4.48z"/><path d="M89.311 36.143l-5.406-5.406v-6.335h11.741v11.741h-6.335z"/></g></svg>
|
||||
<span>Press I for info</span>
|
||||
</div>
|
||||
<div id="buttons">
|
||||
<a class="discord-button" target="_blank" href="https://discord.gg/c5eXyBU">
|
||||
<span>Community</span>
|
||||
<!-- prettier-ignore -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 245 240"><path fill="#FFF" d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zm36.5 0c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/><path fill="#FFF" d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/></svg>
|
||||
</a>
|
||||
<a class="bmc-button" target="_blank" href="https://www.buymeacoffee.com/teoxoy">
|
||||
<span>Donate!</span>
|
||||
<!-- prettier-ignore -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 6 6"><path fill="#fff" d="M5.476.393c-.022.023-.036.055-.036.09v.093h-.093c-.072 0-.13.058-.13.13 0 .072.058.13.13.13h.093v.093c0 .072.058.13.13.13.072 0 .13-.058.13-.13V.836h.093c.072 0 .13-.058.13-.13 0-.072-.058-.13-.13-.13H5.7V.483c0-.072-.058-.13-.13-.13-.037 0-.07.016-.094.04z"/><path fill="#fff" fill-opacity=".2" d="M.83.893c.416 0 .753.338.753.753 0 .416-.337.753-.753.753-.415 0-.753-.337-.753-.753 0-.415.338-.753.753-.753zm0 .649c.058 0 .104.047.104.104 0 .058-.046.104-.104.104-.057 0-.104-.046-.104-.104 0-.057.047-.104.104-.104zM5.268 3.125c.111 0 .201-.091.201-.202 0-.111-.09-.202-.201-.202-.111 0-.201.091-.201.202 0 .111.09.202.201.202z"/><path fill="#fff" d="M1.497 4.284c.111 0 .201-.091.201-.202 0-.111-.09-.201-.201-.201-.111 0-.201.09-.201.201 0 .111.09.202.201.202z"/><path fill="#ff9100" d="M4.675 1.14H2.137l.572 3.642h1.394l.572-3.642z"/><path fill="#fd0" d="M4.383 1.14H2.137l.572 3.642h1.102l.572-3.642z"/><path fill="#fff" d="M1.803.804h3.006v.336H1.803z"/><path d="M1.701.702h3.21v.54h-3.21v-.54zm.204.204v.132h2.802V.906H1.905z"/><path fill="#fff" d="M4.229.258H2.367l-.219.504h2.299L4.229.258z"/><path fill="#050505" d="M4.296.156l.306.708H1.993L2.3.156h1.996zM2.434.36l-.131.3h1.989l-.13-.3H2.434z"/><path d="M4.794 1.037l-.605 3.847H2.413l-.604-3.847h2.985zm-.239.205H2.047l.54 3.438h1.428l.54-3.438z"/><path fill="#fff" d="M4.717 2.168h-2.84l.256 1.449h2.328l.256-1.449z"/><path d="M4.839 2.066L4.546 3.72H2.048l-.292-1.654h3.083zm-.464 1.448l.22-1.244H1.999l.22 1.244h2.156z"/></svg>
|
||||
</a>
|
||||
<a class="github-button" target="_blank" href="https://github.com/Teoxoy/factorio-blueprint-editor">
|
||||
<span>Source</span>
|
||||
<!-- prettier-ignore -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path fill="#181616" d="M320 7.9C143.3 7.9 0 151.17 0 327.9c0 141.39 91.68 261.33 218.85 303.65 16 2.93 21.84-6.95 21.84-15.43 0-7.59-.27-27.72-.43-54.42-89 19.34-107.8-42.9-107.8-42.9-14.55-36.96-35.52-46.8-35.52-46.8-29.06-19.85 2.19-19.45 2.19-19.45 32.12 2.26 49 33 49 33 28.54 48.9 74.89 34.78 93.13 26.59 2.9-20.68 11.17-34.78 20.31-42.78-71-8.08-145.76-35.53-145.76-158.16 0-34.93 12.47-63.5 32.94-85.86-3.29-8.1-14.27-40.64 3.14-84.69 0 0 26.87-8.61 88 32.8a303.32 303.32 0 0 1 160.22 0C461.22 132 488 140.62 488 140.62c17.46 44 6.48 76.59 3.19 84.69 20.51 22.36 32.89 50.93 32.89 85.86 0 122.93-74.82 150-146.11 157.91 11.49 9.88 21.73 29.41 21.73 59.26 0 42.78-.39 77.29-.39 87.78 0 8.56 5.76 18.52 22 15.39C548.4 589.11 640 469.25 640 327.92 640 151.17 496.71 7.9 320 7.9z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
<div id="info-panel">
|
||||
<h1>INFO</h1>
|
||||
<ul class="info-list">
|
||||
<li>To access the inventory and start building press <mark>E</mark></li>
|
||||
<li>To import/export a blueprint string use <mark>ctrl/cmd + C/V</mark></li>
|
||||
<li>Check out the settings area for an in depth look at the settings and <mark>keybinds</mark></li>
|
||||
<li>
|
||||
Leave your suggestions, ideas, new features or bug reports inside the app via the Feedback button or
|
||||
on Github
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Features</h2>
|
||||
<ul>
|
||||
<li>rendering and editing blueprints</li>
|
||||
<li>history (undo/redo)</li>
|
||||
<li>copy and delete selections</li>
|
||||
<li>
|
||||
import blueprints and books from multiple sources (direct bp string, pastebin, hastebin, gist,
|
||||
gitlab, factorioprints, google docs)
|
||||
</li>
|
||||
<li>generating blueprint images</li>
|
||||
<li>oil outpost generator</li>
|
||||
<li>customizable keybinds</li>
|
||||
<li>"creative" entities</li>
|
||||
</ul>
|
||||
<h2>Planned Features</h2>
|
||||
<ul>
|
||||
<li>more blueprint tools</li>
|
||||
<li>more entity editors</li>
|
||||
<li>full rails support</li>
|
||||
<li>trains support</li>
|
||||
<li>mod support</li>
|
||||
</ul>
|
||||
<h3>Avalible url query parameters</h3>
|
||||
<ul>
|
||||
<li>source=<BPSTRING_OR_URL_TO_BPSTRING></li>
|
||||
<li>index=<INDEX_OF_BP_IN_BOOK></li>
|
||||
</ul>
|
||||
<p class="copyright-notice">
|
||||
All art assets, spritesheets and other Factorio game data used in this project belong to Wube Software
|
||||
Ltd and are not for redistribution
|
||||
</p>
|
||||
</div>
|
||||
<canvas id="editor" tabindex="1"></canvas>
|
||||
</body>
|
||||
</html>
|
261
packages/website/src/index.ts
Normal file
261
packages/website/src/index.ts
Normal file
@ -0,0 +1,261 @@
|
||||
import { utils as pixiUtils } from 'pixi.js'
|
||||
import FileSaver from 'file-saver'
|
||||
import { GUIController } from 'dat.gui'
|
||||
import EDITOR, { Blueprint, Book, TrainBlueprintError, ModdedBlueprintError } from '@fbe/editor'
|
||||
import initToasts from './toasts'
|
||||
import initFeedbackButton from './feedbackButton'
|
||||
import initSettingsPane from './settingsPane'
|
||||
|
||||
const CANVAS = document.getElementById('editor') as HTMLCanvasElement
|
||||
|
||||
let bp: Blueprint
|
||||
let book: Book
|
||||
|
||||
const loadingScreen = {
|
||||
el: document.getElementById('loadingScreen'),
|
||||
show() {
|
||||
this.el.classList.add('active')
|
||||
},
|
||||
hide() {
|
||||
this.el.classList.remove('active')
|
||||
}
|
||||
}
|
||||
|
||||
if (pixiUtils.isMobile.any) {
|
||||
loadingScreen.el.classList.add('mobileError')
|
||||
throw new Error('MOBILE DEVICE DETECTED')
|
||||
}
|
||||
|
||||
console.log(
|
||||
'\n%cLooking for the source?\nhttps://github.com/Teoxoy/factorio-blueprint-editor\n',
|
||||
'color: #1f79aa; font-weight: bold'
|
||||
)
|
||||
|
||||
initFeedbackButton()
|
||||
const createToast = initToasts()
|
||||
|
||||
const params = window.location.search.slice(1).split('&')
|
||||
|
||||
let bpSource: string
|
||||
let bpIndex = 0
|
||||
for (const p of params) {
|
||||
if (p.includes('source')) {
|
||||
bpSource = p.split('=')[1]
|
||||
}
|
||||
if (p.includes('index')) {
|
||||
bpIndex = Number(p.split('=')[1])
|
||||
}
|
||||
}
|
||||
|
||||
let guiBPIndex: GUIController
|
||||
|
||||
EDITOR.initEditor(CANVAS)
|
||||
.catch(error => createErrorMessage('Something went wrong.', error))
|
||||
|
||||
.then(() => {
|
||||
if (localStorage.getItem('quickbarItemNames')) {
|
||||
const quickbarItems = JSON.parse(localStorage.getItem('quickbarItemNames'))
|
||||
EDITOR.setQuickbarItems(quickbarItems)
|
||||
}
|
||||
|
||||
registerActions()
|
||||
|
||||
guiBPIndex = initSettingsPane({
|
||||
bp,
|
||||
book
|
||||
}).guiBPIndex
|
||||
})
|
||||
|
||||
.then(() => EDITOR.bpStringEncodeDecode.getBlueprintOrBookFromSource(bpSource))
|
||||
.catch(error => createBPImportError(error))
|
||||
.then(bpOrBook => loadBp(bpOrBook || new Blueprint()))
|
||||
|
||||
.then(() => createWelcomeMessage())
|
||||
.catch(error => createBPImportError(error))
|
||||
|
||||
window.addEventListener('unload', () => {
|
||||
localStorage.setItem('quickbarItemNames', JSON.stringify(EDITOR.getQuickbarItems()))
|
||||
})
|
||||
|
||||
function loadBp(bpOrBook: Blueprint | Book): void {
|
||||
if (bpOrBook instanceof Book) {
|
||||
book = bpOrBook
|
||||
bp = book.getBlueprint(bpIndex ? bpIndex : undefined)
|
||||
EDITOR.loadBlueprint(bp)
|
||||
|
||||
guiBPIndex.max(book.lastBookIndex).setValue(book.activeIndex)
|
||||
} else {
|
||||
bp = bpOrBook
|
||||
EDITOR.loadBlueprint(bpOrBook)
|
||||
|
||||
guiBPIndex.setValue(0).max(0)
|
||||
}
|
||||
|
||||
loadingScreen.hide()
|
||||
|
||||
const bpIsEmpty = bpOrBook instanceof Blueprint && bpOrBook.isEmpty()
|
||||
if (!bpIsEmpty) {
|
||||
createToast({ text: 'Blueprint string loaded successfully', type: 'success' })
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('copy', (e: ClipboardEvent) => {
|
||||
if (document.activeElement !== CANVAS) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
|
||||
if (bp.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
const onSuccess = (): void => {
|
||||
createToast({ text: 'Blueprint string copied to clipboard', type: 'success' })
|
||||
}
|
||||
|
||||
const onError = (error: Error): void => {
|
||||
createErrorMessage('Blueprint string could not be generated.', error)
|
||||
}
|
||||
|
||||
const bpOrBook = book ? book : bp
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
EDITOR.bpStringEncodeDecode
|
||||
.encode(bpOrBook)
|
||||
.then(s => navigator.clipboard.writeText(s))
|
||||
.then(onSuccess)
|
||||
.catch(onError)
|
||||
} else {
|
||||
const data = EDITOR.bpStringEncodeDecode.encodeSync(bpOrBook)
|
||||
if (data.value) {
|
||||
e.clipboardData.setData('text/plain', data.value)
|
||||
onSuccess()
|
||||
} else {
|
||||
onError(data.error)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
document.addEventListener('paste', (e: ClipboardEvent) => {
|
||||
if (document.activeElement !== CANVAS) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
|
||||
loadingScreen.show()
|
||||
|
||||
const promise =
|
||||
navigator.clipboard && navigator.clipboard.readText
|
||||
? navigator.clipboard.readText()
|
||||
: Promise.resolve(e.clipboardData.getData('text'))
|
||||
|
||||
promise
|
||||
.then(EDITOR.bpStringEncodeDecode.getBlueprintOrBookFromSource)
|
||||
.then(loadBp)
|
||||
.catch(error => {
|
||||
loadingScreen.hide()
|
||||
createBPImportError(error)
|
||||
})
|
||||
})
|
||||
|
||||
function registerActions(): void {
|
||||
EDITOR.registerAction('clear', 'shift+n').bind({
|
||||
press: () => {
|
||||
loadBp(new Blueprint())
|
||||
}
|
||||
})
|
||||
|
||||
EDITOR.registerAction('generateOilOutpost', 'g').bind({
|
||||
press: () => {
|
||||
const errorMessage = bp.generatePipes()
|
||||
if (errorMessage) {
|
||||
createToast({ text: errorMessage, type: 'warning' })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
EDITOR.registerAction('info', 'i').bind({
|
||||
press: () => {
|
||||
const infoPanel = document.getElementById('info-panel')
|
||||
if (infoPanel.classList.contains('active')) {
|
||||
infoPanel.classList.remove('active')
|
||||
} else {
|
||||
infoPanel.classList.add('active')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
EDITOR.registerAction('takePicture', 'modifier+s').bind({
|
||||
press: () => {
|
||||
if (bp.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
EDITOR.getPicture().then(blob => {
|
||||
FileSaver.saveAs(blob, `${bp.name}.png`)
|
||||
createToast({ text: 'Blueprint image successfully generated', type: 'success' })
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
EDITOR.importKeybinds(JSON.parse(localStorage.getItem('keybinds')))
|
||||
|
||||
window.addEventListener('unload', () => {
|
||||
const keybinds = EDITOR.exportKeybinds()
|
||||
if (Object.keys(keybinds).length) {
|
||||
localStorage.setItem('keybinds', JSON.stringify(keybinds))
|
||||
} else {
|
||||
localStorage.removeItem('keybinds')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function createWelcomeMessage(): void {
|
||||
const notFirstRun = localStorage.getItem('firstRun') === 'false'
|
||||
if (notFirstRun) {
|
||||
return
|
||||
}
|
||||
localStorage.setItem('firstRun', 'false')
|
||||
|
||||
// Wait a bit just to capture the users attention
|
||||
// This way they will see the toast animation
|
||||
setTimeout(() => {
|
||||
createToast({
|
||||
text:
|
||||
'> To access the inventory and start building press E<br>' +
|
||||
'> To import/export a blueprint string use ctrl/cmd + C/V<br>' +
|
||||
'> For more info press I<br>' +
|
||||
'> Also check out the settings area',
|
||||
timeout: 30000
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
function createErrorMessage(text: string, error: unknown): void {
|
||||
console.error(error)
|
||||
createToast({
|
||||
text:
|
||||
`${text}<br>` +
|
||||
'Please check out the console (F12) for an error message and ' +
|
||||
'report this bug on github or using the feedback button.',
|
||||
type: 'error',
|
||||
timeout: 10000
|
||||
})
|
||||
}
|
||||
function createBPImportError(error: Error | TrainBlueprintError | ModdedBlueprintError): void {
|
||||
if (error instanceof TrainBlueprintError) {
|
||||
createErrorMessage(
|
||||
'Blueprint with train entities not supported yet. If you think this is a mistake:',
|
||||
error.errors
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (error instanceof ModdedBlueprintError) {
|
||||
createErrorMessage(
|
||||
'Blueprint with modded items not supported yet. If you think this is a mistake:',
|
||||
error.errors
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
createErrorMessage('Blueprint string could not be loaded.', error)
|
||||
}
|
@ -1,14 +1,18 @@
|
||||
import { GUI, GUIController } from 'dat.gui'
|
||||
import FD from 'factorio-data'
|
||||
import actions from './actions'
|
||||
import G from './common/globals'
|
||||
import spritesheetsLoader from './spritesheetsLoader'
|
||||
import { oilOutpostSettings } from './factorio-data/blueprint'
|
||||
import EDITOR, { Blueprint, Book } from '@fbe/editor'
|
||||
|
||||
GUI.TEXT_CLOSED = 'Close Settings'
|
||||
GUI.TEXT_OPEN = 'Open Settings'
|
||||
|
||||
export default function initDatGui(): {
|
||||
const COLOR_DARK = 0x303030
|
||||
const COLOR_LIGHT = 0xc9c9c9
|
||||
const isDarkColor = (color: number): boolean => color === COLOR_DARK
|
||||
|
||||
export default function initSettingsPane(shared: {
|
||||
bp: Blueprint
|
||||
book: Book
|
||||
}): {
|
||||
guiBPIndex: GUIController
|
||||
} {
|
||||
const gui = new GUI({
|
||||
@ -34,67 +38,33 @@ export default function initDatGui(): {
|
||||
.add({ bpIndex: 0 }, 'bpIndex', 0, 0, 1)
|
||||
.name('BP Index')
|
||||
.onFinishChange((value: number) => {
|
||||
if (G.book) {
|
||||
G.bp = G.book.getBlueprint(value)
|
||||
G.BPC.clearData()
|
||||
G.BPC.initBP()
|
||||
if (shared.book) {
|
||||
shared.bp = shared.book.getBlueprint(value)
|
||||
EDITOR.loadBlueprint(shared.bp)
|
||||
}
|
||||
})
|
||||
|
||||
if (localStorage.getItem('moveSpeed')) {
|
||||
G.moveSpeed = Number(localStorage.getItem('moveSpeed'))
|
||||
const moveSpeed = Number(localStorage.getItem('moveSpeed'))
|
||||
EDITOR.setMoveSpeed(moveSpeed)
|
||||
}
|
||||
gui.add(G, 'moveSpeed', 5, 20)
|
||||
gui.add({ moveSpeed: EDITOR.getMoveSpeed() }, 'moveSpeed', 5, 20)
|
||||
.name('Move Speed')
|
||||
.onChange((val: boolean) => localStorage.setItem('moveSpeed', val.toString()))
|
||||
|
||||
if (localStorage.getItem('quickbarRows')) {
|
||||
G.quickbarRows = Number(localStorage.getItem('quickbarRows'))
|
||||
}
|
||||
gui.add(G, 'quickbarRows', 1, 5, 1)
|
||||
.name('Quickbar Rows')
|
||||
.onChange((rows: number) => {
|
||||
localStorage.setItem('quickbarRows', rows.toString())
|
||||
G.UI.changeQuickbarRows(rows)
|
||||
.onChange((speed: number) => {
|
||||
localStorage.setItem('moveSpeed', speed.toString())
|
||||
EDITOR.setMoveSpeed(speed)
|
||||
})
|
||||
|
||||
window.addEventListener('unload', () => {
|
||||
localStorage.setItem('quickbarItemNames', JSON.stringify(G.UI.quickbarContainer.serialize()))
|
||||
})
|
||||
|
||||
const entitiesQuality = {
|
||||
'Low. Res PNG 8 (1.52 MB)': 0,
|
||||
'High Res PNG 8 (5.52 MB)': 1,
|
||||
'Low. Res PNG 32 (5.55 MB)': 2,
|
||||
'High Res PNG 32 (18.20 MB)': 3
|
||||
if (localStorage.getItem('debug')) {
|
||||
const debug = Boolean(localStorage.getItem('debug'))
|
||||
EDITOR.setDebugging(debug)
|
||||
}
|
||||
const setQuality = (quality: number): void => {
|
||||
G.quality.hr = quality % 2 === 1
|
||||
G.quality.compressed = quality < 2
|
||||
}
|
||||
|
||||
const gl = document.createElement('canvas').getContext('webgl')
|
||||
if (gl.getParameter(gl.MAX_TEXTURE_SIZE) < 8192) {
|
||||
delete entitiesQuality['High Res PNG 8 (5.52 MB)']
|
||||
delete entitiesQuality['High Res PNG 32 (18.20 MB)']
|
||||
G.quality.hr = false
|
||||
}
|
||||
|
||||
const quality = localStorage.getItem('quality')
|
||||
? Number(localStorage.getItem('quality'))
|
||||
: (G.quality.hr ? 1 : 0) + (G.quality.compressed ? 0 : 2)
|
||||
setQuality(quality)
|
||||
|
||||
gui.add({ quality }, 'quality', entitiesQuality)
|
||||
.name('Entities Quality')
|
||||
.onChange((quality: number) => {
|
||||
localStorage.setItem('quality', quality.toString())
|
||||
setQuality(quality)
|
||||
spritesheetsLoader.changeQuality(G.quality.hr, G.quality.compressed)
|
||||
})
|
||||
|
||||
G.debug = Boolean(localStorage.getItem('debug'))
|
||||
gui.add(G, 'debug')
|
||||
gui.add(
|
||||
{
|
||||
debug: EDITOR.isDebuggingOn()
|
||||
},
|
||||
'debug'
|
||||
)
|
||||
.name('Debug')
|
||||
.onChange((debug: boolean) => {
|
||||
if (debug) {
|
||||
@ -102,46 +72,52 @@ export default function initDatGui(): {
|
||||
} else {
|
||||
localStorage.removeItem('debug')
|
||||
}
|
||||
// TODO: find a nice way to do this
|
||||
G.bp.history.logging = debug
|
||||
G.UI.showDebuggingLayer = debug
|
||||
EDITOR.setDebugging(debug)
|
||||
})
|
||||
|
||||
// Theme folder
|
||||
const themeFolder = gui.addFolder('Theme')
|
||||
// Grid theme folder
|
||||
const themeFolder = gui.addFolder('Grid Theme')
|
||||
|
||||
if (localStorage.getItem('darkTheme')) {
|
||||
G.colors.darkTheme = localStorage.getItem('darkTheme') === 'true'
|
||||
const darkTheme = localStorage.getItem('darkTheme') === 'true'
|
||||
EDITOR.setGridColor(darkTheme ? COLOR_DARK : COLOR_LIGHT)
|
||||
}
|
||||
themeFolder
|
||||
.add(G.colors, 'darkTheme')
|
||||
.add({ darkTheme: isDarkColor(EDITOR.getGridColor()) }, 'darkTheme')
|
||||
.name('Dark Mode')
|
||||
.onChange((val: boolean) => localStorage.setItem('darkTheme', val.toString()))
|
||||
.onChange((darkTheme: boolean) => {
|
||||
localStorage.setItem('darkTheme', darkTheme.toString())
|
||||
EDITOR.setGridColor(darkTheme ? COLOR_DARK : COLOR_LIGHT)
|
||||
})
|
||||
|
||||
if (localStorage.getItem('pattern')) {
|
||||
G.colors.pattern = localStorage.getItem('pattern') as 'checker' | 'grid'
|
||||
const pattern = localStorage.getItem('pattern') as 'checker' | 'grid'
|
||||
EDITOR.setGridPattern(pattern)
|
||||
}
|
||||
themeFolder
|
||||
.add(G.colors, 'pattern', ['checker', 'grid'])
|
||||
.add({ pattern: EDITOR.getGridPattern() }, 'pattern', ['checker', 'grid'])
|
||||
.name('Pattern')
|
||||
.onChange((val: 'checker' | 'grid') => {
|
||||
G.BPC.generateGrid(val)
|
||||
localStorage.setItem('pattern', val)
|
||||
.onChange((pattern: 'checker' | 'grid') => {
|
||||
localStorage.setItem('pattern', pattern)
|
||||
EDITOR.setGridPattern(pattern)
|
||||
})
|
||||
|
||||
if (localStorage.getItem('oilOutpostSettings')) {
|
||||
const settings = JSON.parse(localStorage.getItem('oilOutpostSettings'))
|
||||
Object.keys(oilOutpostSettings).forEach(k => {
|
||||
if (settings[k]) {
|
||||
const S = oilOutpostSettings as Record<string, string | boolean | number>
|
||||
S[k] = settings[k]
|
||||
}
|
||||
})
|
||||
EDITOR.setOilOutpostSettings(settings)
|
||||
}
|
||||
window.addEventListener('unload', () =>
|
||||
localStorage.setItem('oilOutpostSettings', JSON.stringify(oilOutpostSettings))
|
||||
localStorage.setItem('oilOutpostSettings', JSON.stringify(EDITOR.getOilOutpostSettings()))
|
||||
)
|
||||
|
||||
const oilOutpostSettings = new Proxy(EDITOR.getOilOutpostSettings(), {
|
||||
set: (settings, key, value) => {
|
||||
settings[key as string] = value
|
||||
EDITOR.setOilOutpostSettings(settings)
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
const oilOutpostFolder = gui.addFolder('Oil Outpost Generator Settings')
|
||||
oilOutpostFolder.add(oilOutpostSettings, 'DEBUG').name('Debug')
|
||||
oilOutpostFolder.add(oilOutpostSettings, 'PUMPJACK_MODULE', getModulesObjFor('pumpjack')).name('Pumpjack Modules')
|
||||
@ -149,8 +125,14 @@ export default function initDatGui(): {
|
||||
oilOutpostFolder.add(oilOutpostSettings, 'BEACONS').name('Beacons')
|
||||
oilOutpostFolder.add(oilOutpostSettings, 'MIN_AFFECTED_ENTITIES', 1, 12, 1).name('Min Affect. Pumpjacks')
|
||||
oilOutpostFolder.add(oilOutpostSettings, 'BEACON_MODULE', getModulesObjFor('beacon')).name('Beacon Modules')
|
||||
oilOutpostFolder.add(actions.generateOilOutpost, 'call').name('Generate (g)')
|
||||
|
||||
oilOutpostFolder
|
||||
.add(
|
||||
{
|
||||
generate: () => EDITOR.callAction('generateOilOutpost')
|
||||
},
|
||||
'generate'
|
||||
)
|
||||
.name('Generate')
|
||||
function getModulesObjFor(entityName: string): Record<string, string> {
|
||||
return (
|
||||
Object.keys(FD.items)
|
||||
@ -177,11 +159,8 @@ export default function initDatGui(): {
|
||||
// Keybinds folder
|
||||
const keybindsFolder = gui.addFolder('Keybinds')
|
||||
|
||||
actions.forEachAction((action, actionName) => {
|
||||
const name = actionName
|
||||
.split(/(?=[A-Z1-9])/)
|
||||
.join(' ')
|
||||
.replace(/(\b\w)/, c => c.toUpperCase())
|
||||
EDITOR.forEachAction(action => {
|
||||
const name = action.prettyName
|
||||
if (name.includes('Quickbar')) {
|
||||
return
|
||||
}
|
||||
@ -193,11 +172,8 @@ export default function initDatGui(): {
|
||||
|
||||
const quickbarFolder = keybindsFolder.addFolder('Quickbar')
|
||||
|
||||
actions.forEachAction((action, actionName) => {
|
||||
const name = actionName
|
||||
.split(/(?=[A-Z1-9])/)
|
||||
.join(' ')
|
||||
.replace(/(\b\w)/, c => c.toUpperCase())
|
||||
EDITOR.forEachAction(action => {
|
||||
const name = action.prettyName
|
||||
if (!name.includes('Quickbar')) {
|
||||
return
|
||||
}
|
||||
@ -210,7 +186,7 @@ export default function initDatGui(): {
|
||||
keybindsFolder
|
||||
.add(
|
||||
{
|
||||
resetDefaults: () => actions.forEachAction(action => action.resetKeyCombo())
|
||||
resetDefaults: () => EDITOR.resetKeybinds()
|
||||
},
|
||||
'resetDefaults'
|
||||
)
|
15
packages/website/tsconfig.json
Normal file
15
packages/website/tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"outDir": "dist",
|
||||
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"lib": ["dom", "es6", "es2016.array.include"],
|
||||
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
}
|
||||
}
|
164
packages/website/webpack.config.ts
Normal file
164
packages/website/webpack.config.ts
Normal file
@ -0,0 +1,164 @@
|
||||
import path from 'path'
|
||||
import webpack from 'webpack'
|
||||
import { CleanWebpackPlugin } from 'clean-webpack-plugin'
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin'
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
|
||||
import TerserJSPlugin from 'terser-webpack-plugin'
|
||||
import OptimizeCSSAssetsPlugin from 'optimize-css-assets-webpack-plugin'
|
||||
import WebpackDevServer from 'webpack-dev-server'
|
||||
|
||||
const config = (_: unknown, { mode }: { mode: 'production' | 'development' }): webpack.Configuration => {
|
||||
const DEV = mode !== 'production'
|
||||
const sourceMapType = DEV ? 'inline-source-map' : 'source-map'
|
||||
const sourceMap = !!sourceMapType
|
||||
// hmr only works for css in the current config
|
||||
const hmr = true
|
||||
|
||||
return {
|
||||
target: 'web',
|
||||
mode: DEV ? 'development' : 'production',
|
||||
devtool: sourceMapType,
|
||||
devServer: {
|
||||
contentBase: './dist',
|
||||
hot: hmr,
|
||||
before(_, server) {
|
||||
devServer = server
|
||||
}
|
||||
},
|
||||
stats: DEV ? 'minimal' : 'normal',
|
||||
entry: ['./src/index.ts', './src/index.styl'],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
transpileOnly: true,
|
||||
experimentalWatchApi: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.styl$/,
|
||||
use: [
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: {
|
||||
hmr: DEV,
|
||||
sourceMap
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
sourceMap
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
sourceMap
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'stylus-loader',
|
||||
options: {
|
||||
sourceMap
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(jpg|png|svg)$/,
|
||||
use: {
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: DEV ? '[name].[ext]' : '[name].[contenthash:10].[ext]'
|
||||
}
|
||||
}
|
||||
},
|
||||
// {
|
||||
// test: /\.svg$/,
|
||||
// loader: 'svg-inline-loader'
|
||||
// }
|
||||
{
|
||||
test: /\.html$/,
|
||||
use: {
|
||||
loader: 'html-loader',
|
||||
options: {
|
||||
minimize: !DEV,
|
||||
attrs: ['img:src', 'link:href']
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(),
|
||||
new HtmlWebpackPlugin({
|
||||
template: './src/index.html'
|
||||
}),
|
||||
reloadHtml,
|
||||
new MiniCssExtractPlugin({
|
||||
filename: DEV ? '[name].css' : '[name].[contenthash:10].css'
|
||||
})
|
||||
],
|
||||
optimization: {
|
||||
moduleIds: 'hashed',
|
||||
runtimeChunk: 'single',
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
cacheGroups: {
|
||||
factorio_data: {
|
||||
priority: 1,
|
||||
test: /[\\/]node_modules[\\/]factorio-data[\\/]/,
|
||||
name: 'factorio_data'
|
||||
},
|
||||
vendor: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: 'vendor'
|
||||
}
|
||||
}
|
||||
},
|
||||
minimizer: [
|
||||
new TerserJSPlugin({ sourceMap }),
|
||||
new OptimizeCSSAssetsPlugin({ cssProcessorOptions: { map: sourceMap } })
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts', '.styl', '.html']
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: DEV ? '[name].js' : '[name].[contenthash:10].js'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Reload devServer when HTML changes (this doesn't happen automatically because we use HtmlWebpackPlugin)
|
||||
From https://github.com/jantimon/html-webpack-plugin/issues/100#issuecomment-368303060
|
||||
*/
|
||||
let devServer: WebpackDevServer
|
||||
function reloadHtml(): void {
|
||||
const cache: Record<string, string> = {}
|
||||
const plugin = { name: 'CustomHtmlReloadPlugin' }
|
||||
// @ts-ignore
|
||||
this.hooks.compilation.tap(plugin, compilation => {
|
||||
// @ts-ignore
|
||||
compilation.hooks.htmlWebpackPluginAfterEmit.tap(plugin, data => {
|
||||
const orig = cache[data.outputName]
|
||||
const html = data.html.source()
|
||||
// plugin seems to emit on any unrelated change?
|
||||
if (orig && orig !== html) {
|
||||
// @ts-ignore
|
||||
devServer.sockWrite(devServer.sockets, 'content-changed')
|
||||
}
|
||||
cache[data.outputName] = html
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default config
|
@ -1,17 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "es6", "dom.iterable", "es2016.array.include", "es2017.object"],
|
||||
"target": "es6",
|
||||
"module": "es6",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"pretty": true,
|
||||
|
||||
"sourceMap": true,
|
||||
"alwaysStrict": true,
|
||||
"removeComments": true,
|
||||
|
||||
"noImplicitAny": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
"sourceMap": true,
|
||||
"skipLibCheck": true,
|
||||
"alwaysStrict": true
|
||||
|
||||
"preserveConstEnums": true
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user