Code Monkey home page Code Monkey logo

Comments (3)

phoboslab avatar phoboslab commented on July 30, 2024

Related to this, I wrote a minimal implementation of the API to support Weltmeister with NodeJS for my Impact2 branch a few years ago. In my typical style it doesn't need any external packages (no npm). Some paths may need to be adjusted for Impact1. It's supposed to sit in the main directory as server.js and you'd start the server for Weltmeister with node server.js.

https://gist.github.com/phoboslab/0bf09acb2ea4912d621ae80c439a4bba

So, just an idea for an (imho) nice and clean solution: how about we rename this to impact.js, put it in the main directory and add the baking functionality. You could then call it with:

  • node impact.js editor to start the server for Weltmeister
  • node impact.js bake (or build?) to produce the baked output

Edit: looking at this again, it would need some more adjustments. The current Weltmeister API distinguished between a browse and glob command, which could both be served through the new glob command of the server.js alone. This requires some changes in the weltmeister.js and modal-dialogs.js, though. Not sure if it's worth the effort.

from impact.

Joncom avatar Joncom commented on July 30, 2024

I kind of like the simplicity and separation of concerns of having:

  • /tools/bake.js (for baking)
  • /server.js (for the Weltmeister/development server)

Edit: I like your server.js. It looks like a simpler version of impact-dev-server, which I've been using.

from impact.

Joncom avatar Joncom commented on July 30, 2024

Edit: looking at this again, it would need some more adjustments. The current Weltmeister API distinguished between a browse and glob command, which could both be served through the new glob command of the server.js alone. This requires some changes in the weltmeister.js and modal-dialogs.js, though. Not sure if it's worth the effort.

I got it working.

Here's a patch to get Weltmeister running off node server.js. It looks bigger than it really is, due to indentation.

diff --git a/lib/weltmeister/config.js b/lib/weltmeister/config.js
index 8d99632..cf29d1b 100644
--- a/lib/weltmeister/config.js
+++ b/lib/weltmeister/config.js
@@ -119,7 +119,6 @@ wm.config = {
 	// to change these.
 	'api': {
 		'save': 'lib/weltmeister/api/save.php',
-		'browse': 'lib/weltmeister/api/browse.php',
 		'glob': 'lib/weltmeister/api/glob.php'
 	}
 };
diff --git a/lib/weltmeister/entities.js b/lib/weltmeister/entities.js
index 68fa638..5f7fd8e 100644
--- a/lib/weltmeister/entities.js
+++ b/lib/weltmeister/entities.js
@@ -7,52 +7,49 @@ ig.module(
 .defines(function(){ "use strict";
 
 // Load the list of entity files via AJAX PHP glob
-var path = wm.config.api.glob + '?',
+var moduleNames = [],
+    modules = {},
     globs = typeof wm.config.project.entityFiles == 'string' ? 
         [wm.config.project.entityFiles] : 
         wm.config.project.entityFiles;
-    
-for (var i = 0; i < globs.length; i++) {
-    path += 'glob[]=' + encodeURIComponent(globs[i]) + '&';
-}
-
-path += 'nocache=' + Math.random();
-	
-var req = $.ajax({
-	url: path, 
-	method: 'get',
-	dataType: 'json',
 	
-	// MUST load synchronous, as the engine would otherwise determine that it
-	// can't resolve dependencies to weltmeister.entities when there are
-	// no more files to load and weltmeister.entities is still not defined
-	// because the ajax request hasn't finished yet.
-	// FIXME FFS!
-	async: false, 
-	success: function(files) {
+for (let i = 0; i < globs.length; i++) {
+	var dir = globs[i].substr(0, globs[i].indexOf('*'));
+	var req = $.ajax({
+		url: wm.config.api.glob + '?dir=' + encodeURIComponent(dir) + '&type=js',
+		method: 'get',
+		dataType: 'json',
 		
-		// File names to Module names
-		var moduleNames = [];
-		var modules = {};
-		for( var i = 0; i < files.length; i++ ) {
-			var name = files[i]
-				.replace(new RegExp("^"+ig.lib+"|\\.js$", "g"), '')
-				.replace(/\//g, '.');
-			moduleNames.push( name );
-			modules[name] = files[i];
+		// MUST load synchronous, as the engine would otherwise determine that it
+		// can't resolve dependencies to weltmeister.entities when there are
+		// no more files to load and weltmeister.entities is still not defined
+		// because the ajax request hasn't finished yet.
+		// FIXME FFS!
+		async: false, 
+		success: function(data) {
+			
+			// File names to Module names
+			var files = data.files;
+			for( var i = 0; i < files.length; i++ ) {
+				var name = files[i]
+					.replace(new RegExp("^"+ig.lib+"|\\.js$", "g"), '')
+					.replace(/\//g, '.');
+				moduleNames.push( name );
+				modules[name] = files[i];
+			}
+		},
+		error: function( xhr, status, error ){
+			throw( 
+				"Failed to load entity list via glob.php: " + error + "\n" +
+				xhr.responseText
+			);
 		}
-		
-		// Define a Module that requires all entity Modules
-		ig.module('weltmeister.entities')
-			.requires.apply(ig, moduleNames)
-			.defines(function(){ wm.entityModules = modules; });
-	},
-	error: function( xhr, status, error ){
-		throw( 
-			"Failed to load entity list via glob.php: " + error + "\n" +
-			xhr.responseText
-		);
-	}
-});
+	});
+}
+
+// Define a Module that requires all entity Modules
+ig.module('weltmeister.entities')
+	.requires.apply(ig, moduleNames)
+	.defines(function(){ wm.entityModules = modules; });
 
 });
\ No newline at end of file
diff --git a/lib/weltmeister/modal-dialogs.js b/lib/weltmeister/modal-dialogs.js
index 53ff3cd..cc920bd 100644
--- a/lib/weltmeister/modal-dialogs.js
+++ b/lib/weltmeister/modal-dialogs.js
@@ -93,7 +93,7 @@ wm.ModalDialogPathSelect = wm.ModalDialog.extend({
 		this.parent();
 		this.pathInput = $('<input/>', {'type': 'text', 'class': 'modalDialogPath'} );
 		this.buttonDiv.before( this.pathInput );
-		this.pathDropdown = new wm.SelectFileDropdown( this.pathInput, wm.config.api.browse, this.fileType );
+		this.pathDropdown = new wm.SelectFileDropdown( this.pathInput, wm.config.api.glob, this.fileType );
 	},
 	
 	
diff --git a/lib/weltmeister/select-file-dropdown.js b/lib/weltmeister/select-file-dropdown.js
index 6975a1d..4dfbb83 100644
--- a/lib/weltmeister/select-file-dropdown.js
+++ b/lib/weltmeister/select-file-dropdown.js
@@ -59,14 +59,12 @@ wm.SelectFileDropdown = ig.Class.extend({
 			this.div.append( parentDir );
 		}
 		for( var i = 0; i < data.dirs.length; i++ ) {
-			var name = data.dirs[i].match(/[^\/]*$/)[0] + '/';
-			var dir = $('<a/>', {'class':'dir', href:data.dirs[i], html: name, title: name});
+			var dir = $('<a/>', {'class':'dir', href: data.dirs[i].path, html: data.dirs[i].name, title: data.dirs[i].name});
 			dir.bind( 'click', this.selectDir.bind(this) );
 			this.div.append( dir );
 		}
 		for( var i = 0; i < data.files.length; i++ ) {
-			var name = data.files[i].match(/[^\/]*$/)[0];
-			var file = $('<a/>', {'class':'file', href:data.files[i], html: name, title: name});
+			var file = $('<a/>', {'class':'file', href: data.files[i].path, html: data.files[i].name, title: data.files[i].name});
 			file.bind( 'click', this.selectFile.bind(this) );
 			this.div.append( file );
 		}
diff --git a/lib/weltmeister/weltmeister.js b/lib/weltmeister/weltmeister.js
index 772e531..ceb70a3 100644
--- a/lib/weltmeister/weltmeister.js
+++ b/lib/weltmeister/weltmeister.js
@@ -64,13 +64,13 @@ wm.Weltmeister = ig.Class.extend({
 			
 		
 		// Dialogs
-		this.loadDialog = new wm.ModalDialogPathSelect( 'Load Level', 'Load', 'scripts' );
+		this.loadDialog = new wm.ModalDialogPathSelect( 'Load Level', 'Load', wm.Weltmeister.levelType );
 		this.loadDialog.onOk = this.load.bind(this);
 		this.loadDialog.setPath( wm.config.project.levelPath );
 		$('#levelLoad').bind( 'click', this.showLoadDialog.bind(this) );
 		$('#levelNew').bind( 'click', this.showNewDialog.bind(this) );
 		
-		this.saveDialog = new wm.ModalDialogPathSelect( 'Save Level', 'Save', 'scripts' );
+		this.saveDialog = new wm.ModalDialogPathSelect( 'Save Level', 'Save', wm.Weltmeister.levelType );
 		this.saveDialog.onOk = this.save.bind(this);
 		this.saveDialog.setPath( wm.config.project.levelPath );
 		$('#levelSaveAs').bind( 'click', this.saveDialog.open.bind(this.saveDialog) );
@@ -84,7 +84,7 @@ wm.Weltmeister = ig.Class.extend({
 		this.mode = this.MODE.DEFAULT;
 		
 		
-		this.tilesetSelectDialog = new wm.SelectFileDropdown( '#layerTileset', wm.config.api.browse, 'images' );
+		this.tilesetSelectDialog = new wm.SelectFileDropdown( '#layerTileset', wm.config.api.glob, wm.Weltmeister.imageType );
 		this.entities = new wm.EditEntities( $('#layerEntities') );
 		
 		$('#layers').sortable({
@@ -455,16 +455,12 @@ wm.Weltmeister = ig.Class.extend({
 				"});";
 		}
 		
-		var postString = 
-			'path=' + encodeURIComponent( path ) +
-			'&data=' + encodeURIComponent(dataString);
-		
 		var req = $.ajax({
-			url: wm.config.api.save,
+			url: wm.config.api.save + '?path=' + encodeURIComponent( path ),
 			type: 'POST',
 			dataType: 'json',
 			async: false,
-			data: postString,
+			data: dataString,
 			success:this.saveResponse.bind(this)
 		});
 	},
@@ -937,6 +933,9 @@ wm.Weltmeister.getMaxHeight = function() {
 	return $(window).height() - $('#headerMenu').height();
 };
 
+wm.Weltmeister.levelType = 'js,json';
+wm.Weltmeister.imageType = 'png,jpg,jpeg,gif';
+
 
 // Custom ig.Image class for use in Weltmeister. To make the zoom function 
 // work, we need some additional scaling behavior:
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..cfdc077
--- /dev/null
+++ b/server.js
@@ -0,0 +1,155 @@
+'use strict';
+
+let http = require('http'),
+	url = require('url'),
+	querystring = require('querystring'),
+	fs = require('fs'),
+	port = parseInt(process.argv[2] || 8080, 10);
+
+
+let API = {
+	glob: function(params, body, callback) {
+		let dir = params.dir ? params.dir.replace(/\/$/, '') : '';
+		fs.readdir('./' + dir, function(err, files){
+			if (err) {
+				return callback('dir listing failed for ./' + dir);
+			}
+
+			let parent = dir.replace(/\/?[^\/]*$/,'');
+			let ret = {current: dir, parent: parent, dirs: [], files: []},
+				typeMatch = (params.type ? params.type.split(',') : [])
+						.map(type => new RegExp('\\.' + type + '$'));
+
+			for (let i = 0; i < files.length; i++) {
+				let fileName = files[i],
+					filePath = (dir && dir+'/') + fileName;
+
+				if (fileName.match(/^\./)) {
+					continue;
+				}
+
+				if (fs.statSync('./' + filePath).isDirectory()) {
+					ret.dirs.push({name: fileName, path: filePath});
+				}
+				else if (
+					typeMatch.length === 0 ||
+					typeMatch.find(pattern => fileName.match(pattern))
+				) {
+					ret.files.push({name: fileName, path: filePath});
+				}
+			}
+			callback(null, ret);
+		});
+	},
+
+	save: function(params, body, callback) {
+		if (!params.path) {
+			return callback('no path specified');
+		}
+
+		fs.writeFile(params.path, body, {flag: 'w'}, function(err) {
+			if (err) {
+				return callback('error writing file ' + params.path);
+			}
+			callback(null, {saved: true});
+		});
+	}
+};
+
+
+
+let utf8 = '; charset=utf-8';
+let mime = {
+	html: 'text/html' + utf8,
+	htm: 'text/html' + utf8,
+	json: 'application/json' + utf8,
+	js: 'application/javascript' + utf8,
+	jpeg: 'image/jpeg',
+	jpg: 'image/jpeg',
+	gif: 'image/gif',
+	png: 'image/png',
+	ogg: 'audio/ogg',
+	mp3: 'audio/mp3',
+	aac: 'audio/aac',
+	css: 'text/css' + utf8,
+	bin: 'application/octet-stream'
+};
+
+let serveAPI = function(request, response, name) {
+	let uri = url.parse(request.url, true),
+		apiFunc = API[name];
+
+	if (!apiFunc) {
+		return writeError(request, response, 404, 'no such API function: ' + name);
+	}
+
+	console.log('API:', name, uri.query);
+
+	let bodyChunks = [];
+	request.on('data', function(chunk){
+		bodyChunks.push(chunk);
+	});
+
+	request.on('end', function(){
+		let body = Buffer.concat(bodyChunks);
+		apiFunc(uri.query, body, function(err, data){
+			if (err) {
+				writeError(request, response, 500, err);
+			}
+			else {
+				response.writeHead(200, {'Content-Type': mime.json});
+				response.write(JSON.stringify(data));
+				response.end();
+			}
+		});
+	});
+};
+
+let serveStaticFile = function(request, response) {
+	let uri = querystring.unescape(url.parse(request.url).pathname),
+		fileName = './' + uri;
+
+	fs.stat(fileName, function(err, stat) {
+		if (err) {
+			return writeError(request, response, 404, 'not found');
+		}
+
+		if (stat.isDirectory()) {
+			fileName += '/index.html';
+		}
+
+		fs.readFile(fileName, 'binary', function(err, file) {
+			if (err) {
+				return writeError(request, response, 500, 'internal error');
+			}
+
+			let ext = fileName.match(/\.(\w+)$/);
+			console.log('200: ' + uri);
+			response.writeHead(200, {'content-type': (ext && mime[ext[1]]) || mime.bin});
+			response.write(file, 'binary');
+			response.end();
+		});
+	});
+};
+
+let writeError = function(request, response, code, message) {
+	console.log(code+': ' + request.url);
+	response.writeHead(code, {'Content-Type': mime.json});
+	response.write(JSON.stringify({error: {code: code, message: message}}));
+	response.end();
+};
+
+http.createServer(function(request, response) {
+	let apiMatch = null;
+	if (apiMatch = request.url.match(/\/api\/(\w+)/)) {
+		serveAPI(request, response, apiMatch[1]);
+	}
+	else {
+		serveStaticFile(request, response);
+	}
+}).listen(port);
+
+console.log(
+	'Listening on http://localhost:' + port + '/\n' +
+	'CTRL + C to shutdown'
+);

The browse API call doesn't exist anymore (it uses glob).

Since your version of glob is slightly different, and also because the level filename in your version is set via the URL (instead of as part of the POST data), this is no longer backwards compatible with the old PHP back-end. Think it is important to keep the PHP back-end working? Would it possibly make sense to remove it?

Also tweaked your version to filter multiple file types at once, because the original Weltmeister shows only "js/json" files in the level drop-down, and only "png/jpg/jpeg/gif" files in tile-sheet drop-down.

Any thoughts to make this cleaner?

from impact.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.