This commit is contained in:
Andrey Kolosov 2019-08-01 18:02:20 +03:00
parent ca67365fe7
commit 1c7c0684c7
15 changed files with 3617 additions and 2717 deletions

View File

@ -72,7 +72,9 @@ module.exports = {
appBuild: resolveApp('build'),
appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'),
appHtmlStorage: resolveApp('public/storage.html'),
appIndexJs: resolveModule(resolveApp, 'src/index'),
appStorageJs: resolveModule(resolveApp, 'src/storage'),
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('src'),
appTsConfig: resolveApp('tsconfig.json'),

View File

@ -128,25 +128,28 @@ module.exports = function(webpackEnv) {
: isEnvDevelopment && 'cheap-module-source-map',
// These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle.
entry: [
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
// Finally, this is your app's code:
paths.appIndexJs,
// We include the app code last so that if there is a runtime error during
// initialization, it doesn't blow up the WebpackDevServer client, and
// changing JS code would still trigger a refresh.
].filter(Boolean),
entry: {
main: [
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
// Finally, this is your app's code:
paths.appIndexJs,
// We include the app code last so that if there is a runtime error during
// initialization, it doesn't blow up the WebpackDevServer client, and
// changing JS code would still trigger a refresh.
].filter(Boolean),
storage: paths.appStorageJs,
},
output: {
// The build folder.
path: isEnvProduction ? paths.appBuild : undefined,
@ -500,6 +503,7 @@ module.exports = function(webpackEnv) {
{},
{
inject: true,
chunks: ['main'],
template: paths.appHtml,
},
isEnvProduction
@ -520,6 +524,33 @@ module.exports = function(webpackEnv) {
: undefined
)
),
new HtmlWebpackPlugin(
Object.assign(
{},
{
inject: true,
chunks: ['storage'],
filename: 'storage.html',
template: paths.appHtmlStorage,
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined
)
),
// Inlines the webpack runtime script. This script is too small to warrant
// a network request.
isEnvProduction &&

5992
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -64,7 +64,8 @@
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js"
"test": "node scripts/test.js",
"deploy": "gh-pages -d build"
},
"eslintConfig": {
"extends": "react-app"
@ -131,8 +132,10 @@
"react-app"
]
},
"homepage": "https://d07riv.github.io/diabloweb",
"devDependencies": {
"exports-loader": "^0.7.0",
"gh-pages": "^2.0.1",
"node-sass": "^4.12.0",
"worker-loader": "^2.0.0"
}

BIN
public/icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
public/icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -6,10 +6,20 @@
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
"display": "fullscreen",
"theme_color": "#ffffff",
"background_color": "#000000"
}

9
public/storage.html Normal file
View File

@ -0,0 +1,9 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DIABLO Connector</title>
</head>
<body>
</body>
</html>

View File

@ -46,7 +46,7 @@ class App extends React.Component {
touchMods = [false, false, false, false, false, false];
touchBelt = [-1, -1, -1, -1, -1, -1];
fs = create_fs(this);
fs = create_fs(true);
constructor(props) {
super(props);
@ -344,6 +344,11 @@ class App extends React.Component {
this.touchButton = touchOther;
if (touchOther) {
this.setTouchMod(touchOther.index, true);
if (touchOther.index === TOUCH_MOVE) {
this.setTouchMod(TOUCH_RMB, false);
} else if (touchOther.index === TOUCH_RMB) {
this.setTouchMod(TOUCH_MOVE, false);
}
delete this.panPos;
} else if (touches.length === 2) {
const x = (touches[1].clientX + touches[0].clientX) / 2, y = (touches[1].clientY + touches[0].clientY) / 2;
@ -406,6 +411,10 @@ class App extends React.Component {
const {x, y} = this.mousePos(prevTc);
this.game("DApi_Mouse", 2, 1, this.eventMods(e), x, y);
this.game("DApi_Mouse", 2, 2, this.eventMods(e), x, y);
if (this.touchMods[TOUCH_RMB] && (!this.touchButton || this.touchButton.index !== TOUCH_RMB)) {
this.setTouchButton(TOUCH_RMB, false);
}
}
if (!document.fullscreenElement) {
this.element.requestFullscreen();
@ -469,22 +478,20 @@ class App extends React.Component {
This is a web port of the original Diablo game, based on source code reconstructed by
GalaXyHaXz and devilution team: <Link href="https://github.com/diasurgical/devilution">https://github.com/diasurgical/devilution</Link>
</p>
<form>
<p>
If you own the original game, you can drop the original DIABDAT.MPQ onto this page or click the button below to start playing.
The game can be purchased from <Link href="https://www.gog.com/game/diablo">GoG</Link>.
</p>
{!has_spawn && (
<p>
If you own the original game, you can drop the original DIABDAT.MPQ onto this page (or <label htmlFor="loadFile" className="link" onClick={this.download}>click here</label>)
to start playing. The game can be purchased from <Link href="https://www.gog.com/game/diablo">GoG</Link>.
</p>
<input accept=".mpq" type="file" id="loadFile" style={{display: "none"}} onChange={this.parseFile}/>
</form>
{has_spawn ? (
<span className="startButton" onClick={() => this.start()}>Play Shareware</span>
) : (
<p>
Or you can download and play the shareware version instead (50MB download). <i>The site has lately been under too much stress due to users downloading the shareware
package, so instead you will need to <a href="https://d07riv.github.io/diabloweb/public/spawn.mpq">download it from GitHub</a>, then drop it on the page (or upload
by <label htmlFor="loadFile" className="link" onClick={this.download}>clicking here</label>).</i>
Or you can play the shareware version for free (50MB download).
</p>
)}
<form>
<label htmlFor="loadFile" className="startButton">Select MPQ</label>
<input accept=".mpq" type="file" id="loadFile" style={{display: "none"}} onChange={this.parseFile}/>
</form>
<span className="startButton" onClick={() => this.start()}>Play Shareware</span>
</div>
)}
</div>

View File

@ -79,7 +79,7 @@ body, #root, .App {
text-align: center;
background: #000;
p {
margin: 12px 0;
margin: 10px 0;
}
.startButton {
display: inline-block;
@ -88,6 +88,8 @@ body, #root, .App {
font-size: 2em;
padding: 4px 18px;
cursor: pointer;
margin-top: 6px;
width: 90%;
&:hover {
background-color: #111;
}

View File

@ -1,4 +1,4 @@
//import axios from 'axios';
import axios from 'axios';
const SpawnSize = 50274091;
@ -12,13 +12,12 @@ export default async function load_spawn(api, fs) {
file = null;
}
if (!file) {
throw Error("Invalid spawn.mpq size.");
/*const spawn = await axios.request({
const spawn = await axios.request({
url: '/spawn.mpq',
responseType: 'arraybuffer',
onDownloadProgress: e => {
if (api.onProgress) {
api.onProgress({type: 'spawn', loaded: e.loaded, total: e.total || SpawnSize});
api.onProgress({text: 'Downloading...', loaded: e.loaded, total: e.total || SpawnSize});
}
},
headers: {
@ -30,7 +29,7 @@ export default async function load_spawn(api, fs) {
}
const data = new Uint8Array(spawn.data);
fs.files.set('spawn.mpq', data);
fs.update('spawn.mpq', data);*/
fs.update('spawn.mpq', data.slice());
}
return fs;
}

View File

@ -1,22 +1,63 @@
import IdbKvStore from 'idb-kv-store';
export default async function create_fs() {
const importStorage = () => new Promise((resolve, reject) => {
let done = false;
const frame = document.createElement('iframe');
window.addEventListener('message', ({data}) => {
if (data.method === 'storage' && !done) {
done = true;
resolve(data.files);
frame.contentWindow.postMessage({method: 'clear'}, '*');
}
});
frame.addEventListener('load', () => {
frame.contentWindow.postMessage({method: 'transfer'}, '*');
});
frame.addEventListener('error', () => {
if (!done) {
done = true;
resolve(null);
}
});
frame.src = "https://diablo.rivsoft.net/storage.html";
frame.style.display = "none";
document.body.appendChild(frame);
setTimeout(() => {
if (!done) {
done = true;
resolve(null);
}
}, 10000);
});
export default async function create_fs(load) {
try {
const store = new IdbKvStore('diablo_fs');
const files = new Map();
for (let [name, data] of Object.entries(await store.json())) {
files.set(name, data);
}
if (load) {
const files = await importStorage();
if (files) {
for (let [name, data] of files) {
files.set(name, data);
store.set(name, data);
}
}
}
return {
files,
update: (name, data) => store.set(name, data),
delete: name => store.remove(name),
clear: () => store.clear(),
};
} catch (e) {
return {
files: new Map(),
update: () => Promise.resolve(),
delete: () => Promise.resolve(),
clear: () => Promise.resolve(),
};
}
}

View File

@ -1,7 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './reset.css';
import * as serviceWorker from './serviceWorker';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.register();

135
src/serviceWorker.js Normal file
View File

@ -0,0 +1,135 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

12
src/storage.js Normal file
View File

@ -0,0 +1,12 @@
import create_fs from './fs';
const fs = create_fs();
window.addEventListener('message', ({data, source}) => {
if (data.method === 'transfer') {
fs.then(({files}) => {
source.postMessage({method: 'storage', files}, '*');
});
} else if (data.method === 'clear') {
fs.then(({clear}) => clear());
}
});