<commit_before>const randomstring = require("randomstring");

module.exports = (message, client, helper) => {
    let options = message.content.split(" ");
    let url = options[1];

    if (!client.steam_connected) {
        message.channel.createMessage("Steam is down or registering is disabled at the moment, sorry about that.").catch(err => helper.handle(message, err));
        return;
    }

    if (!url) {
        message.channel.createMessage("Please give me something to register!").catch(err => helper.handle(message, err));
        return;
    }

    client.core.util.resolve_steam_url(client, url).then(acc_id => {
        helper.log(message, `register: ${acc_id}`);

        client.mika.getPlayer(acc_id).then(res => {
            if (!res.profile) {
                message.channel.createMessage("This steam account is private! I can't register it!").catch(err => helper.handle(message, err));
                helper.log(message, "  failed on private steam account");
                return;
            }

            if (res.profile.steamid) {
                let rand = randomstring.generate(6);
                client.redis.set(`register:${res.profile.steamid}`, `${rand}:${message.author.id}`, (err) => {
                    if (err) helper.log(message, err);
                    client.redis.expire(`register:${res.profile.steamid}`, 900);
                    client.redis.publish("discord", JSON.stringify({
                        "code": 3,
                        "message": "registering a user",
                        "steam_id": res.profile.steamid
                    }));
                    message.author.getDMChannel().then(dm_channel => {
                        dm_channel.createMessage([
                            "Thanks for registering with listen-bot!",
                            "Please check your steam friend requests. You should recieve a request from me.",
                            `On acceptance, I will prompt you for a code, please send me this: \`${rand}\``,
                            "This code is case sensitive and will expire in 10 minutes.",
                            "",
                            `If you didn't get a friend request, try sending me a request as well: <${client.config.steam_acc_url}>`
                        ].join("\n")).then(() => {
                            helper.log(message, "sent friend req, awaiting code");
                        }).catch(err => helper.handle(message, err));
                    });
                });
            } else {
                client.createMessage("I couldn't find a steam ID associated with this user!").catch(err => helper.handle(message, err));
            }
        }).catch(err => {
            helper.log(message, "something went wrong with mika");
            helper.log(message, err);
        });
    }).catch(err => {
        if (err == "nosteam") {
            message.channel.createMessage("I couldn't find a steam profile in that message!").catch(err => helper.handle(message, err));
        } else if (err.success == 42) {
            message.channel.createMessage("I couldn't find a steam profile associated with that link!").catch(err => helper.handle(message, err));

        } else {
            message.channel.createMessage("Something went wrong resolving this profile.").catch(err => helper.handle(message, err));
            helper.log(message, "something went wrong resolving a url");
            helper.log(message, err);
        }
    });
};
<commit_msg>be omre clear<commit_after>const randomstring = require("randomstring");

module.exports = (message, client, helper) => {
    let options = message.content.split(" ");
    let url = options[1];

    if (!client.steam_connected) {
        message.channel.createMessage("Steam is down or registering is disabled at the moment, sorry about that.").catch(err => helper.handle(message, err));
        return;
    }

    if (!url) {
        message.channel.createMessage("Please give me something to register!").catch(err => helper.handle(message, err));
        return;
    }

    client.core.util.resolve_steam_url(client, url).then(acc_id => {
        helper.log(message, `register: ${acc_id}`);

        client.mika.getPlayer(acc_id).then(res => {
            if (!res.profile) {
                message.channel.createMessage("This steam account is private, or hasn't signed up for OpenDota yet! I can't register it!").catch(err => helper.handle(message, err));
                helper.log(message, "  failed on private steam account");
                return;
            }

            if (res.profile.steamid) {
                let rand = randomstring.generate(6);
                client.redis.set(`register:${res.profile.steamid}`, `${rand}:${message.author.id}`, (err) => {
                    if (err) helper.log(message, err);
                    client.redis.expire(`register:${res.profile.steamid}`, 900);
                    client.redis.publish("discord", JSON.stringify({
                        "code": 3,
                        "message": "registering a user",
                        "steam_id": res.profile.steamid
                    }));
                    message.author.getDMChannel().then(dm_channel => {
                        dm_channel.createMessage([
                            "Thanks for registering with listen-bot!",
                            "Please check your steam friend requests. You should recieve a request from me.",
                            `On acceptance, I will prompt you for a code, please send me this: \`${rand}\``,
                            "This code is case sensitive and will expire in 10 minutes.",
                            "",
                            `If you didn't get a friend request, try sending me a request as well: <${client.config.steam_acc_url}>`
                        ].join("\n")).then(() => {
                            helper.log(message, "sent friend req, awaiting code");
                        }).catch(err => helper.handle(message, err));
                    });
                });
            } else {
                client.createMessage("I couldn't find a steam ID associated with this user!").catch(err => helper.handle(message, err));
            }
        }).catch(err => {
            helper.log(message, "something went wrong with mika");
            helper.log(message, err);
        });
    }).catch(err => {
        if (err == "nosteam") {
            message.channel.createMessage("I couldn't find a steam profile in that message!").catch(err => helper.handle(message, err));
        } else if (err.success == 42) {
            message.channel.createMessage("I couldn't find a steam profile associated with that link!").catch(err => helper.handle(message, err));

        } else {
            message.channel.createMessage("Something went wrong resolving this profile.").catch(err => helper.handle(message, err));
            helper.log(message, "something went wrong resolving a url");
            helper.log(message, err);
        }
    });
};
<|endoftext|>


<commit_before>var upload = require('../lib/upload');
var retrieve = require('../lib/retrieve');
var resize = require('../lib/resize');
var filters = require('../lib/filters');
var helpers = require('../lib/helperfunctions');
var db = require('../app/models/imageMetaData');
var uuid = require('node-uuid');

module.exports = {
  routeHandler: function(app) {
    //middleware for any image retrieval
    app.param('image', function(req, res, next, key) {
      //if image exists in db, set key as a property of
      //the request object
      db.find({key: key}, function(error, data) {
          if (error) {
            return next(error);
          }

          if (data.length === 0) {
            console.error('image has not been uploaded </3');
            return helpers.write(req, res, 404);
          }

          req.key = key;
          next();
        });
    });

    //middlware for filter requests
    app.param('filter', function(req, res, next, filter) {
      req.filter = filter;
      next();
    });

    //home route
    var home = require('../app/controllers/home');
    app.get('/', home.index);

    //upload route
    app.post('/upload', upload.upload, function(req, res, next) {
    });

    //get image route
    app.get('/:image', retrieve.retrieve, function(req, res, next) {
      helpers.helper.write(req, res, 200);
    });

    //resize image route
    app.get('/:image/size', retrieve.retrieve, resize.identify, function(req, res, next) {
    });

    //transform image
    app.get('/:image/:filter', retrieve.retrieve, filters.routeFilter, function(req, res, next) {
    });
  },
};

<commit_msg>fix syntax error<commit_after>var upload = require('../lib/upload');
var retrieve = require('../lib/retrieve');
var resize = require('../lib/resize');
var filters = require('../lib/filters');
var helpers = require('../lib/helperfunctions');
var db = require('../app/models/imageMetaData');
var uuid = require('node-uuid');

module.exports = {
  routeHandler: function(app) {
    //middleware for any image retrieval
    app.param('image', function(req, res, next, key) {
      //if image exists in db, set key as a property of
      //the request object
      db.find({key: key}, function(error, data) {
          if (error) {
            return next(error);
          }

          if (data.length === 0) {
            console.error('image has not been uploaded </3');
            return helpers.helper.write(req, res, 404);
          }

          req.key = key;
          next();
        });
    });

    //middlware for filter requests
    app.param('filter', function(req, res, next, filter) {
      req.filter = filter;
      next();
    });

    //home route
    var home = require('../app/controllers/home');
    app.get('/', home.index);

    //upload route
    app.post('/upload', upload.upload, function(req, res, next) {
    });

    //get image route
    app.get('/:image', retrieve.retrieve, function(req, res, next) {
      helpers.helper.write(req, res, 200);
    });

    //resize image route
    app.get('/:image/size', retrieve.retrieve, resize.identify, function(req, res, next) {
    });

    //transform image
    app.get('/:image/:filter', retrieve.retrieve, filters.routeFilter, function(req, res, next) {
    });
  },
};

<|endoftext|>


<commit_before>
var EventEmitter = require('events').EventEmitter,
	logger = require('../logging').getLogger(__LOGGER__),
	Router = require('routr'),
	Q = require('q'),
	History = require("../components/History"),
	PageUtil = require("../util/PageUtil");

var _ = {
	isFunction: require('lodash/lang/isFunction'),
};

class Navigator extends EventEmitter {

	constructor (context, routes) {
		super();

		this.router = new Router(routes.routes);
		this.context = context;

		this._globalMiddleware = routes.middleware;
		this._loading = false;
		this._currentRoute = null;
		this._nextRoute = null;
	}

	/**
	 * type is one of
	 *    History.events.PUSHSTATE: user clicked something to go forward but browser didn't do a
	 * full page load
	 *    History.events.POPSTATE: user clicked back button but browser didn't do a full page load
	 *    History.events.PAGELOAD: full browser page load, not using History API.
	 *
	 * Default is History.events.PAGELOAD.
	 */
	navigate (request, type) {
		logger.debug(`Navigating to ${request.getUrl()}`);
		type = type || History.events.PAGELOAD;

		// If we're running without client navigation and this is a
		// navigation away from the initial page we'll just let the
		// browser handle this request as a normal page load.
		if (global.__tritonDisableClientNavigation && this._haveInitialized){
			window.location.href = request.getUrl();
			return;
		}

		this._haveInitialized = true;

		var route = this.router.getRoute(request.getUrl(), {navigate: {path:request.getUrl(), type:type}});

		if (route) {
			logger.debug(`Mapped ${request.getUrl()} to route ${route.name}`);
		} else if (!request._frameback) {
			setTimeout( () => {
				this.emit('navigateDone', { status: 404, message: "No Route!" }, null, request.getUrl(), type);
			}, 0);
			return;
		}

		// We may or may not _actually_ start this route client side.
		//
		// If there's a flurry of navigation we skip any routes that
		// blow by while we're still working on a page, and only
		// finally start the _last_ one.
		//
		// The promise returned from `startRoute()` will be rejected
		// if we're not going to proceed, so resources will be freed.
		//
		this.startRoute(route, request, type).then(() => {
			if (this._ignoreCurrentNavigation){
				// This is a one-time deal.
				this._ignoreCurrentNavigation = false;
				return;
			}

			/* Breathe... */

			var loaders = route.config.page;

			// Normalize.
			// The page object may either directly be a loader or
			// it may be an object whose values are loaders.
			if (_.isFunction(loaders)){
				loaders = {'default': loaders};
			}

			var loadPage = loaders.default;

			var mobileDetect = this.context.getMobileDetect();

			['phone', 'tablet'].forEach(format => {

				if (loaders[format] && mobileDetect[format]()){

					// Need to disambiguate for bundleNameUtil.
					route.name += '-'+format;

					loadPage = loaders[format];
				}
			});

			loadPage().done(pageConstructor => {
				if (request.setRoute) {
					request.setRoute(route);
				}
				this.handlePage(pageConstructor, request, type);

			}, err => {
				console.error("Error resolving page", err);
			});

		});

	}

	// If you call this you're responsible for calling `finishRoute()`
	// when you're done with whatever it is you're hiding from the
	// navigator.
	ignoreCurrentNavigation() {
		this._ignoreCurrentNavigation = true;
	}

	handlePage(pageConstructor, request, type) {
		// instantiate the pages we need to fulfill this request.
		var pageClasses = [];

		this._addPageMiddlewareToArray(this._globalMiddleware, pageClasses);
		this._addPageMiddlewareToArray([pageConstructor], pageClasses);

		var pages = pageClasses.map((pageClass) => {
			if (Object.getOwnPropertyNames(pageClass).length === 0) {
				throw new Error("Tried to instantiate a page or middleware class that was an empty object. Did you forget to assign a class to module.exports?");
			}
			return new pageClass();
		});
		var page = PageUtil.createPageChain(pages);

		page.setRequest(request);

		PageUtil.PageConfig.initFromPageWithDefaults(page, {
			isFragment    : false,
			isRawResponse : false,
		});

		// call page.handleRoute(), and use the resulting code to decide how to
		// respond.
		page.handleRoute().then(handleRouteResult => {

			page.setStatus(handleRouteResult.code);

			// TODO: I think that 3xx/4xx/5xx shouldn't be considered "errors" in navigateDone, but that's
			// how the code is structured right now, and I'm changing too many things at once at the moment. -sra.
			if (handleRouteResult.code && ((handleRouteResult.code / 100)|0) !== 2) {
				this.emit("navigateDone", {status: handleRouteResult.code, redirectUrl: handleRouteResult.location}, null, request.getUrl(), type);
				return;
			}
			if (handleRouteResult.page) {
				// in this case, we should forward to a new page *without* changing the URL. Since we are already
				// in an async callback, we should schedule a new handlePage with the new page constructor and return
				// from this call.
				setTimeout(() => this.handlePage(handleRouteResult.page, request, type), 0);
				return;
			}

			this.emit('navigateDone', null, page, request.getUrl(), type);
		}).catch(err => {
			logger.error("Error while handling route", err);
			this.emit('navigateDone', {status: 500}, page, request.getUrl(), type);
		});

	}

	/**
	 * recursively adds the middleware in the pages array to array.
	 */
	_addPageMiddlewareToArray(pages, array) {
		if (!pages) return;
		pages.forEach((page) => {
			if (page.middleware) {
				this._addPageMiddlewareToArray(page.middleware(), array);
			}
			array.push(page);
		});
	}

	getState () {
		return {
			loading: this._loading,
			route: this._currentRoute,
		}
	}

	getCurrentRoute () {
		return this._currentRoute;
	}

	getLoading () {
		return this._loading;
	}

	startRoute (route, request, type) {

		// If we're being called with a requested route, we'll need to
		// tell the caller when they can proceed with their
		// navigation.
		var dfd, promise;

		// We need to handle the case where routes are requested while
		// we're handling the previous navigation.  This can happen if
		// the user furiously clicks the browser's forward/back
		// navigation buttons.
		//
		// We don't want a _queue_ here, because we're only ultimately
		// going to show the user the _final_ route that's requested,
		// so we'll just keep a single reference to the next route we
		// need to actually render once our current navigation is
		// complete.
		//
		if (request) {

			// We don't want to leave navigation detritus
			// laying around as we discard bypassed pages.
			if (this._nextRoute) this._nextRoute[1].reject();

			dfd = Q.defer(), promise = dfd.promise;

			this._nextRoute = [route, request, type, dfd];
		}

		// If we're _currently_ navigating, we'll wait to start the
		// next route until this navigation is complete.  Interleaved
		// navigation causes all kinds of havoc.
		if (!this._loading && this._nextRoute){

			[route, request, type, dfd] = this._nextRoute;

			this._loading      = true;
			this._currentRoute = route;
			this._nextRoute    = null;

			this.emit('navigateStart', {route, request, type});

			// This allows the actual navigation to
			// proceed.
			dfd.resolve();
		}

		return promise;
	}

	finishRoute () {
		this._loading = false;

		// If other routes were queued while we were navigating, we'll
		// start the next one right off.
		//
		this.startRoute();
	}
}

module.exports = Navigator;
<commit_msg>PLAT-428: Add 'mobile' option for union of phone/tablet<commit_after>
var EventEmitter = require('events').EventEmitter,
	logger = require('../logging').getLogger(__LOGGER__),
	Router = require('routr'),
	Q = require('q'),
	History = require("../components/History"),
	PageUtil = require("../util/PageUtil");

var _ = {
	isFunction: require('lodash/lang/isFunction'),
};

class Navigator extends EventEmitter {

	constructor (context, routes) {
		super();

		this.router = new Router(routes.routes);
		this.context = context;

		this._globalMiddleware = routes.middleware;
		this._loading = false;
		this._currentRoute = null;
		this._nextRoute = null;
	}

	/**
	 * type is one of
	 *    History.events.PUSHSTATE: user clicked something to go forward but browser didn't do a
	 * full page load
	 *    History.events.POPSTATE: user clicked back button but browser didn't do a full page load
	 *    History.events.PAGELOAD: full browser page load, not using History API.
	 *
	 * Default is History.events.PAGELOAD.
	 */
	navigate (request, type) {
		logger.debug(`Navigating to ${request.getUrl()}`);
		type = type || History.events.PAGELOAD;

		// If we're running without client navigation and this is a
		// navigation away from the initial page we'll just let the
		// browser handle this request as a normal page load.
		if (global.__tritonDisableClientNavigation && this._haveInitialized){
			window.location.href = request.getUrl();
			return;
		}

		this._haveInitialized = true;

		var route = this.router.getRoute(request.getUrl(), {navigate: {path:request.getUrl(), type:type}});

		if (route) {
			logger.debug(`Mapped ${request.getUrl()} to route ${route.name}`);
		} else if (!request._frameback) {
			setTimeout( () => {
				this.emit('navigateDone', { status: 404, message: "No Route!" }, null, request.getUrl(), type);
			}, 0);
			return;
		}

		// We may or may not _actually_ start this route client side.
		//
		// If there's a flurry of navigation we skip any routes that
		// blow by while we're still working on a page, and only
		// finally start the _last_ one.
		//
		// The promise returned from `startRoute()` will be rejected
		// if we're not going to proceed, so resources will be freed.
		//
		this.startRoute(route, request, type).then(() => {
			if (this._ignoreCurrentNavigation){
				// This is a one-time deal.
				this._ignoreCurrentNavigation = false;
				return;
			}

			/* Breathe... */

			var loaders = route.config.page;

			// Normalize.
			// The page object may either directly be a loader or
			// it may be an object whose values are loaders.
			if (_.isFunction(loaders)){
				loaders = {'default': loaders};
			}

			var loadPage = loaders.default;

			var mobileDetect = this.context.getMobileDetect();

			// Note that 'mobile' is the _union_ of 'phone' and
			// 'tablet'.  If you _really_ want an iPad and an
			// iPhone to get the _same_ non-desktop experience,
			// use that.
			['phone', 'tablet', 'mobile'].forEach(format => {

				if (loaders[format] && mobileDetect[format]()){

					// Need to disambiguate for bundleNameUtil.
					route.name += '-'+format;

					loadPage = loaders[format];
				}
			});

			loadPage().done(pageConstructor => {
				if (request.setRoute) {
					request.setRoute(route);
				}
				this.handlePage(pageConstructor, request, type);

			}, err => {
				console.error("Error resolving page", err);
			});

		});

	}

	// If you call this you're responsible for calling `finishRoute()`
	// when you're done with whatever it is you're hiding from the
	// navigator.
	ignoreCurrentNavigation() {
		this._ignoreCurrentNavigation = true;
	}

	handlePage(pageConstructor, request, type) {
		// instantiate the pages we need to fulfill this request.
		var pageClasses = [];

		this._addPageMiddlewareToArray(this._globalMiddleware, pageClasses);
		this._addPageMiddlewareToArray([pageConstructor], pageClasses);

		var pages = pageClasses.map((pageClass) => {
			if (Object.getOwnPropertyNames(pageClass).length === 0) {
				throw new Error("Tried to instantiate a page or middleware class that was an empty object. Did you forget to assign a class to module.exports?");
			}
			return new pageClass();
		});
		var page = PageUtil.createPageChain(pages);

		page.setRequest(request);

		PageUtil.PageConfig.initFromPageWithDefaults(page, {
			isFragment    : false,
			isRawResponse : false,
		});

		// call page.handleRoute(), and use the resulting code to decide how to
		// respond.
		page.handleRoute().then(handleRouteResult => {

			page.setStatus(handleRouteResult.code);

			// TODO: I think that 3xx/4xx/5xx shouldn't be considered "errors" in navigateDone, but that's
			// how the code is structured right now, and I'm changing too many things at once at the moment. -sra.
			if (handleRouteResult.code && ((handleRouteResult.code / 100)|0) !== 2) {
				this.emit("navigateDone", {status: handleRouteResult.code, redirectUrl: handleRouteResult.location}, null, request.getUrl(), type);
				return;
			}
			if (handleRouteResult.page) {
				// in this case, we should forward to a new page *without* changing the URL. Since we are already
				// in an async callback, we should schedule a new handlePage with the new page constructor and return
				// from this call.
				setTimeout(() => this.handlePage(handleRouteResult.page, request, type), 0);
				return;
			}

			this.emit('navigateDone', null, page, request.getUrl(), type);
		}).catch(err => {
			logger.error("Error while handling route", err);
			this.emit('navigateDone', {status: 500}, page, request.getUrl(), type);
		});

	}

	/**
	 * recursively adds the middleware in the pages array to array.
	 */
	_addPageMiddlewareToArray(pages, array) {
		if (!pages) return;
		pages.forEach((page) => {
			if (page.middleware) {
				this._addPageMiddlewareToArray(page.middleware(), array);
			}
			array.push(page);
		});
	}

	getState () {
		return {
			loading: this._loading,
			route: this._currentRoute,
		}
	}

	getCurrentRoute () {
		return this._currentRoute;
	}

	getLoading () {
		return this._loading;
	}

	startRoute (route, request, type) {

		// If we're being called with a requested route, we'll need to
		// tell the caller when they can proceed with their
		// navigation.
		var dfd, promise;

		// We need to handle the case where routes are requested while
		// we're handling the previous navigation.  This can happen if
		// the user furiously clicks the browser's forward/back
		// navigation buttons.
		//
		// We don't want a _queue_ here, because we're only ultimately
		// going to show the user the _final_ route that's requested,
		// so we'll just keep a single reference to the next route we
		// need to actually render once our current navigation is
		// complete.
		//
		if (request) {

			// We don't want to leave navigation detritus
			// laying around as we discard bypassed pages.
			if (this._nextRoute) this._nextRoute[1].reject();

			dfd = Q.defer(), promise = dfd.promise;

			this._nextRoute = [route, request, type, dfd];
		}

		// If we're _currently_ navigating, we'll wait to start the
		// next route until this navigation is complete.  Interleaved
		// navigation causes all kinds of havoc.
		if (!this._loading && this._nextRoute){

			[route, request, type, dfd] = this._nextRoute;

			this._loading      = true;
			this._currentRoute = route;
			this._nextRoute    = null;

			this.emit('navigateStart', {route, request, type});

			// This allows the actual navigation to
			// proceed.
			dfd.resolve();
		}

		return promise;
	}

	finishRoute () {
		this._loading = false;

		// If other routes were queued while we were navigating, we'll
		// start the next one right off.
		//
		this.startRoute();
	}
}

module.exports = Navigator;
<|endoftext|>


<commit_before>const debug = require('@tryghost/debug')('frontend');
const path = require('path');
const express = require('../../shared/express');
const cors = require('cors');
const {URL} = require('url');
const errors = require('@tryghost/errors');

// App requires
const config = require('../../shared/config');
const constants = require('@tryghost/constants');
const storage = require('../../server/adapters/storage');
const urlService = require('../../server/services/url');
const urlUtils = require('../../shared/url-utils');
const sitemapHandler = require('../services/sitemap/handler');
const appService = require('../services/apps');
const themeEngine = require('../services/theme-engine');
const themeMiddleware = themeEngine.middleware;
const membersService = require('../../server/services/members');
const offersService = require('../../server/services/offers');
const customRedirects = require('../../server/services/redirects');
const siteRoutes = require('./routes');
const shared = require('../../server/web/shared');
const errorHandler = require('@tryghost/mw-error-handler');
const mw = require('./middleware');

const STATIC_IMAGE_URL_PREFIX = `/${urlUtils.STATIC_IMAGE_URL_PREFIX}`;
const STATIC_MEDIA_URL_PREFIX = `/${constants.STATIC_MEDIA_URL_PREFIX}`;
const STATIC_FILES_URL_PREFIX = `/${constants.STATIC_FILES_URL_PREFIX}`;

let router;

const corsOptionsDelegate = function corsOptionsDelegate(req, callback) {
    const origin = req.header('Origin');
    const corsOptions = {
        origin: false, // disallow cross-origin requests by default
        credentials: true // required to allow admin-client to login to private sites
    };

    if (!origin || origin === 'null') {
        return callback(null, corsOptions);
    }

    let originUrl;
    try {
        originUrl = new URL(origin);
    } catch (err) {
        return callback(new errors.BadRequestError({err}));
    }

    // originUrl will definitely exist here because according to WHATWG URL spec
    // The class constructor will either throw a TypeError or return a URL object
    // https://url.spec.whatwg.org/#url-class

    // allow all localhost and 127.0.0.1 requests no matter the port
    if (originUrl.hostname === 'localhost' || originUrl.hostname === '127.0.0.1') {
        corsOptions.origin = true;
    }

    // allow the configured host through on any protocol
    const siteUrl = new URL(config.get('url'));
    if (originUrl.host === siteUrl.host) {
        corsOptions.origin = true;
    }

    // allow the configured admin:url host through on any protocol
    if (config.get('admin:url')) {
        const adminUrl = new URL(config.get('admin:url'));
        if (originUrl.host === adminUrl.host) {
            corsOptions.origin = true;
        }
    }

    callback(null, corsOptions);
};

function SiteRouter(req, res, next) {
    router(req, res, next);
}

module.exports = function setupSiteApp(options = {}) {
    debug('Site setup start', options);

    const siteApp = express('site');

    // ## App - specific code
    // set the view engine
    siteApp.set('view engine', 'hbs');

    // enable CORS headers (allows admin client to hit front-end when configured on separate URLs)
    siteApp.use(cors(corsOptionsDelegate));

    siteApp.use(offersService.middleware);

    // you can extend Ghost with a custom redirects file
    // see https://github.com/TryGhost/Ghost/issues/7707
    siteApp.use(customRedirects.middleware);

    // (Optionally) redirect any requests to /ghost to the admin panel
    siteApp.use(mw.redirectGhostToAdmin());

    // Static content/assets
    // @TODO make sure all of these have a local 404 error handler
    // Favicon
    siteApp.use(mw.serveFavicon());

    // Serve sitemap.xsl file
    siteApp.use(mw.servePublicFile('static', 'sitemap.xsl', 'text/xsl', constants.ONE_DAY_S));

    // Serve stylesheets for default templates
    siteApp.use(mw.servePublicFile('static', 'public/ghost.css', 'text/css', constants.ONE_HOUR_S));
    siteApp.use(mw.servePublicFile('static', 'public/ghost.min.css', 'text/css', constants.ONE_YEAR_S));

    // Card assets
    siteApp.use(mw.servePublicFile('built', 'public/cards.min.css', 'text/css', constants.ONE_YEAR_S));
    siteApp.use(mw.servePublicFile('built', 'public/cards.min.js', 'application/javascript', constants.ONE_YEAR_S));

    // Serve blog images using the storage adapter
    siteApp.use(STATIC_IMAGE_URL_PREFIX, mw.handleImageSizes, storage.getStorage('images').serve());
    // Serve blog media using the storage adapter
    siteApp.use(STATIC_MEDIA_URL_PREFIX, storage.getStorage('media').serve());
    // Serve blog files using the storage adapter
    siteApp.use(STATIC_FILES_URL_PREFIX, storage.getStorage('files').serve());

    // Global handling for member session, ensures a member is logged in to the frontend
    siteApp.use(membersService.middleware.loadMemberSession);

    // /member/.well-known/* serves files (e.g. jwks.json) so it needs to be mounted before the prettyUrl mw to avoid trailing slashes
    siteApp.use('/members/.well-known', (req, res, next) => membersService.api.middleware.wellKnown(req, res, next));

    // setup middleware for internal apps
    // @TODO: refactor this to be a proper app middleware hook for internal apps
    config.get('apps:internal').forEach((appName) => {
        const app = require(path.join(config.get('paths').internalAppPath, appName));

        if (Object.prototype.hasOwnProperty.call(app, 'setupMiddleware')) {
            app.setupMiddleware(siteApp);
        }
    });

    // Theme static assets/files
    siteApp.use(mw.staticTheme());
    debug('Static content done');

    // Theme middleware
    // This should happen AFTER any shared assets are served, as it only changes things to do with templates
    siteApp.use(themeMiddleware);
    debug('Themes done');

    // Serve robots.txt if not found in theme
    siteApp.use(mw.servePublicFile('static', 'robots.txt', 'text/plain', constants.ONE_HOUR_S));

    // site map - this should probably be refactored to be an internal app
    sitemapHandler(siteApp);
    debug('Internal apps done');

    // Add in all trailing slashes & remove uppercase
    // must happen AFTER asset loading and BEFORE routing
    siteApp.use(shared.middleware.prettyUrls);

    // ### Caching
    siteApp.use(function (req, res, next) {
        // Site frontend is cacheable UNLESS request made by a member or blog is in private mode
        if (req.member || res.isPrivateBlog) {
            return shared.middleware.cacheControl('private')(req, res, next);
        } else {
            return shared.middleware.cacheControl('public', {maxAge: config.get('caching:frontend:maxAge')})(req, res, next);
        }
    });

    debug('General middleware done');

    router = siteRoutes(options);
    Object.setPrototypeOf(SiteRouter, router);

    // Set up Frontend routes (including private blogging routes)
    siteApp.use(SiteRouter);

    // ### Error handlers
    siteApp.use(errorHandler.pageNotFound);
    config.get('apps:internal').forEach((appName) => {
        const app = require(path.join(config.get('paths').internalAppPath, appName));

        if (Object.prototype.hasOwnProperty.call(app, 'setupErrorHandling')) {
            app.setupErrorHandling(siteApp);
        }
    });
    siteApp.use(mw.errorHandler.handleThemeResponse);

    debug('Site setup end');

    return siteApp;
};

module.exports.reload = ({apiVersion}) => {
    // https://github.com/expressjs/express/issues/2596
    router = siteRoutes({start: true, apiVersion});
    Object.setPrototypeOf(SiteRouter, router);

    // re-initialize apps (register app routers, because we have re-initialized the site routers)
    appService.init();

    // connect routers and resources again
    urlService.queue.start({
        event: 'init',
        tolerance: 100,
        requiredSubscriberCount: 1
    });
};
<commit_msg>Updated caching policy for /members/.well-known<commit_after>const debug = require('@tryghost/debug')('frontend');
const path = require('path');
const express = require('../../shared/express');
const cors = require('cors');
const {URL} = require('url');
const errors = require('@tryghost/errors');

// App requires
const config = require('../../shared/config');
const constants = require('@tryghost/constants');
const storage = require('../../server/adapters/storage');
const urlService = require('../../server/services/url');
const urlUtils = require('../../shared/url-utils');
const sitemapHandler = require('../services/sitemap/handler');
const appService = require('../services/apps');
const themeEngine = require('../services/theme-engine');
const themeMiddleware = themeEngine.middleware;
const membersService = require('../../server/services/members');
const offersService = require('../../server/services/offers');
const customRedirects = require('../../server/services/redirects');
const siteRoutes = require('./routes');
const shared = require('../../server/web/shared');
const errorHandler = require('@tryghost/mw-error-handler');
const mw = require('./middleware');

const STATIC_IMAGE_URL_PREFIX = `/${urlUtils.STATIC_IMAGE_URL_PREFIX}`;
const STATIC_MEDIA_URL_PREFIX = `/${constants.STATIC_MEDIA_URL_PREFIX}`;
const STATIC_FILES_URL_PREFIX = `/${constants.STATIC_FILES_URL_PREFIX}`;

let router;

const corsOptionsDelegate = function corsOptionsDelegate(req, callback) {
    const origin = req.header('Origin');
    const corsOptions = {
        origin: false, // disallow cross-origin requests by default
        credentials: true // required to allow admin-client to login to private sites
    };

    if (!origin || origin === 'null') {
        return callback(null, corsOptions);
    }

    let originUrl;
    try {
        originUrl = new URL(origin);
    } catch (err) {
        return callback(new errors.BadRequestError({err}));
    }

    // originUrl will definitely exist here because according to WHATWG URL spec
    // The class constructor will either throw a TypeError or return a URL object
    // https://url.spec.whatwg.org/#url-class

    // allow all localhost and 127.0.0.1 requests no matter the port
    if (originUrl.hostname === 'localhost' || originUrl.hostname === '127.0.0.1') {
        corsOptions.origin = true;
    }

    // allow the configured host through on any protocol
    const siteUrl = new URL(config.get('url'));
    if (originUrl.host === siteUrl.host) {
        corsOptions.origin = true;
    }

    // allow the configured admin:url host through on any protocol
    if (config.get('admin:url')) {
        const adminUrl = new URL(config.get('admin:url'));
        if (originUrl.host === adminUrl.host) {
            corsOptions.origin = true;
        }
    }

    callback(null, corsOptions);
};

function SiteRouter(req, res, next) {
    router(req, res, next);
}

module.exports = function setupSiteApp(options = {}) {
    debug('Site setup start', options);

    const siteApp = express('site');

    // ## App - specific code
    // set the view engine
    siteApp.set('view engine', 'hbs');

    // enable CORS headers (allows admin client to hit front-end when configured on separate URLs)
    siteApp.use(cors(corsOptionsDelegate));

    siteApp.use(offersService.middleware);

    // you can extend Ghost with a custom redirects file
    // see https://github.com/TryGhost/Ghost/issues/7707
    siteApp.use(customRedirects.middleware);

    // (Optionally) redirect any requests to /ghost to the admin panel
    siteApp.use(mw.redirectGhostToAdmin());

    // Static content/assets
    // @TODO make sure all of these have a local 404 error handler
    // Favicon
    siteApp.use(mw.serveFavicon());

    // Serve sitemap.xsl file
    siteApp.use(mw.servePublicFile('static', 'sitemap.xsl', 'text/xsl', constants.ONE_DAY_S));

    // Serve stylesheets for default templates
    siteApp.use(mw.servePublicFile('static', 'public/ghost.css', 'text/css', constants.ONE_HOUR_S));
    siteApp.use(mw.servePublicFile('static', 'public/ghost.min.css', 'text/css', constants.ONE_YEAR_S));

    // Card assets
    siteApp.use(mw.servePublicFile('built', 'public/cards.min.css', 'text/css', constants.ONE_YEAR_S));
    siteApp.use(mw.servePublicFile('built', 'public/cards.min.js', 'application/javascript', constants.ONE_YEAR_S));

    // Serve blog images using the storage adapter
    siteApp.use(STATIC_IMAGE_URL_PREFIX, mw.handleImageSizes, storage.getStorage('images').serve());
    // Serve blog media using the storage adapter
    siteApp.use(STATIC_MEDIA_URL_PREFIX, storage.getStorage('media').serve());
    // Serve blog files using the storage adapter
    siteApp.use(STATIC_FILES_URL_PREFIX, storage.getStorage('files').serve());

    // Global handling for member session, ensures a member is logged in to the frontend
    siteApp.use(membersService.middleware.loadMemberSession);

    // /member/.well-known/* serves files (e.g. jwks.json) so it needs to be mounted before the prettyUrl mw to avoid trailing slashes
    siteApp.use(
        '/members/.well-known',
        shared.middleware.cacheControl('public', {maxAge: 60 * 60 * 24}),
        (req, res, next) => membersService.api.middleware.wellKnown(req, res, next)
    );

    // setup middleware for internal apps
    // @TODO: refactor this to be a proper app middleware hook for internal apps
    config.get('apps:internal').forEach((appName) => {
        const app = require(path.join(config.get('paths').internalAppPath, appName));

        if (Object.prototype.hasOwnProperty.call(app, 'setupMiddleware')) {
            app.setupMiddleware(siteApp);
        }
    });

    // Theme static assets/files
    siteApp.use(mw.staticTheme());
    debug('Static content done');

    // Theme middleware
    // This should happen AFTER any shared assets are served, as it only changes things to do with templates
    siteApp.use(themeMiddleware);
    debug('Themes done');

    // Serve robots.txt if not found in theme
    siteApp.use(mw.servePublicFile('static', 'robots.txt', 'text/plain', constants.ONE_HOUR_S));

    // site map - this should probably be refactored to be an internal app
    sitemapHandler(siteApp);
    debug('Internal apps done');

    // Add in all trailing slashes & remove uppercase
    // must happen AFTER asset loading and BEFORE routing
    siteApp.use(shared.middleware.prettyUrls);

    // ### Caching
    siteApp.use(function (req, res, next) {
        // Site frontend is cacheable UNLESS request made by a member or blog is in private mode
        if (req.member || res.isPrivateBlog) {
            return shared.middleware.cacheControl('private')(req, res, next);
        } else {
            return shared.middleware.cacheControl('public', {maxAge: config.get('caching:frontend:maxAge')})(req, res, next);
        }
    });

    debug('General middleware done');

    router = siteRoutes(options);
    Object.setPrototypeOf(SiteRouter, router);

    // Set up Frontend routes (including private blogging routes)
    siteApp.use(SiteRouter);

    // ### Error handlers
    siteApp.use(errorHandler.pageNotFound);
    config.get('apps:internal').forEach((appName) => {
        const app = require(path.join(config.get('paths').internalAppPath, appName));

        if (Object.prototype.hasOwnProperty.call(app, 'setupErrorHandling')) {
            app.setupErrorHandling(siteApp);
        }
    });
    siteApp.use(mw.errorHandler.handleThemeResponse);

    debug('Site setup end');

    return siteApp;
};

module.exports.reload = ({apiVersion}) => {
    // https://github.com/expressjs/express/issues/2596
    router = siteRoutes({start: true, apiVersion});
    Object.setPrototypeOf(SiteRouter, router);

    // re-initialize apps (register app routers, because we have re-initialized the site routers)
    appService.init();

    // connect routers and resources again
    urlService.queue.start({
        event: 'init',
        tolerance: 100,
        requiredSubscriberCount: 1
    });
};
<|endoftext|>


<commit_before>"use strict"

const asset = require('./asset')
const colorDepth = require('./converter/colorDepth')
const clamp = require('lodash/clamp')
const glob = require('glob')
const createMappoSession = require('./createMappoSession')
const createMappoMap = require('./createMappoMap')
const createMappoTileset = require('./createMappoTileset')
const detectFormat = require('./detectFormat')
const path = require('path')
const fs = require('fs')
const setupKeyboard = require('./setupKeyboard')
const renderTileHighlightInvertedOutline = require('./renderTileHighlightInvertedOutline')
const renderTileHighlightInvertedSolid = require('./renderTileHighlightInvertedSolid')
const renderTileHighlightColorOutline = require('./renderTileHighlightColorOutline')
const renderTile = require('./renderTile')
const renderLayer = require('./renderLayer')
const renderTileset = require('./renderTileset')
const createCheckerboardPattern = require('./createCheckerboardPattern')

// DOM REFERENCES
const pageTitle = document.querySelector('title')
const mapList = document.querySelector('.map-list')
const layerList = document.querySelector('.layer-list')
const canvas = document.querySelector('.mappo-viewport')
const context = canvas.getContext('2d')
const tilesetCanvasContainer = document.querySelector('.tileset-canvas-container')
const tilesetCanvas = document.querySelector('.tileset-canvas')
const tilesetContext = tilesetCanvas.getContext('2d')
const tilesetSelectedTileCanvas = document.querySelector('.tileset-selected-tile-canvas')
const tilesetSelectedTileContext = tilesetSelectedTileCanvas.getContext('2d')
const tilesetSelectedTileIndex = document.querySelector('.tileset-selected-tile-index')
const tilesetHoveringTileCanvas = document.querySelector('.tileset-hovering-tile-canvas')
const tilesetHoveringTileContext = tilesetHoveringTileCanvas.getContext('2d')
const tilesetHoveringTileIndex = document.querySelector('.tileset-hovering-tile-index')
const middlePanel = document.querySelector('.middle-panel')

const checkerboardPattern = createCheckerboardPattern({document})

const launchFolder = 'data' // TODO(chuck): temp hack for windows. empty string dunna work
console.log('launchFolder', launchFolder)
const mapGlob = '**/*.map'
const mapFilenames = glob.sync(mapGlob, {nocase: true})
const mappoSession = createMappoSession({
  fileSystem: {
    files: mapFilenames
  },
  launchFolder
})

const viewportScales = [0.25, 0.5, 0.75, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const DEFAULT_SCALE_INDEX = 4
const defaultMappoState = {
  scaleIndex: DEFAULT_SCALE_INDEX,
  isLoading: true,
  map: null,
  mapLayerOrder: null,
  mapLayerSelected: null,
  tileset: null,
  tilesetHoverTileX: 0,
  tilesetHoverTileY: 0,
  tilesetHoverIndex: 0,
  tilesetSelectedTileX: 0,
  tilesetSelectedTileY: 0,
  tilesetSelectedIndex: 0,
  cameraX: 0,
  cameraY: 0,
  cameraMoveX: 0,
  cameraMoveY: 0,
  cameraScrollAmount: 1,
  keyPressed: {},
}
let mappoState = Object.assign({}, defaultMappoState)

const getScale = () => viewportScales[mappoState.scaleIndex]

mappoSession.getMapFilenames().forEach(mapFilename => {
  const li = document.createElement('li')
  li.setAttribute('title', mapFilename)
  li.innerText = mapFilename
  // TODO(chuck): temp hack for windows. figure out better launchFolder shenanigans
  li.addEventListener('click', event => {
    loadMap('data/' + mapFilename)
    pageTitle.innerText = 'Mappo - ' + mapFilename
  })
  mapList.appendChild(li)
})

const refreshMapLayerList = () => {
  layerList.innerHTML = ''
  mappoState.map.tileLayers.forEach((layer, index) => {
    const li = document.createElement('li')
    li.setAttribute('title', layer.description)
    li.innerText = layer.description
    li.classList.add('layer-list-item')
    if (mappoState.mapLayerSelected === layer) {
      li.classList.add('selected')
    }
    li.addEventListener('click', event => {
      // TODO(chuck): use a proper element?
      if (event.offsetX < 35) {
        li.classList.toggle('is-layer-hidden')
        layer.isHidden = !layer.isHidden
      }
      mappoState.mapLayerSelected = layer
      const layerListItems = document.querySelectorAll('.layer-list .layer-list-item')
      layerListItems.forEach(layerListItem => {
        layerListItem.classList.remove('selected')
      })
      li.classList.add('selected')
    })
    layerList.appendChild(li)
  })
}

const loadMap = mapFilename => {
  console.group()
  try {
    mappoState = Object.assign({}, defaultMappoState)
    mappoState.isLoading = true

    mappoState.cameraX = 0
    mappoState.cameraY = 0

    const mapBuffer = fs.readFileSync(mapFilename)
    const mapFormat = detectFormat(mapBuffer)
    if (!mapFormat.includes('map')) {
      console.error('expected map but got', mapFormat)
      return
    }
    console.log('mapFormat', mapFormat)
    let mapData
    try {
      mapData = asset.fromBuffer(mapBuffer, asset[mapFormat])
    } catch (exception) {
      console.log(exception)
      return
    }
    mappoState.map = createMappoMap({map: mapData})
    console.log(mapFilename, mapData)
    mappoState.mapLayerSelected = mappoState.map.tileLayers[mappoState.map.mapLayerOrder[0]]
    refreshMapLayerList()

    const vspFilename = path.join(
      path.dirname(mapFilename),
      mappoState.map.tilesetFilename
    )
    const vspBuffer = fs.readFileSync(vspFilename)
    const vspFormat = detectFormat(vspBuffer)
    console.log('vspFormat', vspFormat)
    let vspData
    try {
      vspData = asset.fromBuffer(vspBuffer, asset[vspFormat])
    } catch (exception) {
      console.log(exception)
      return
    }
    mappoState.tileset = createMappoTileset({context, tileset: vspData})
    console.log(vspFilename, vspData)
    tilesetSelectedTileCanvas.width = mappoState.tileset.tileWidth
    tilesetSelectedTileCanvas.height = mappoState.tileset.tileHeight
    tilesetHoveringTileCanvas.width = mappoState.tileset.tileWidth
    tilesetHoveringTileCanvas.height = mappoState.tileset.tileHeight

    mappoState.tileset.imageBitmapPromise.then(() => {
      mappoState.isLoading = false
      resizeCanvas()
    })
  } catch (exception) {
    console.error('ack!', exception)
  } finally {
    console.groupEnd()
  }
}

const keyboard = setupKeyboard({
  addEventListener: document.addEventListener,
  keyPressed: mappoState.keyPressed,
})

let mousedown = false
let mousein = false
middlePanel.addEventListener('mousedown', event => {
  mousedown = true
})

let hoverCanvasCoord = null
let autoScrollX = 0
let autoScrollY = 0
middlePanel.addEventListener('mousemove', event => {
  if (mappoState.isLoading) {
    return
  }

  const scale = getScale()
  if (mousedown) {
    moveCamera(
      -event.movementX / scale,
      -event.movementY / scale
    )
  }

  autoScrollX = 0
  autoScrollY = 0
  const autoScrollThreshold = (mappoState.tileset.tileWidth + mappoState.tileset.tileHeight) / 2;
  if (mousein) {
    const mouseX = event.offsetX
    const mouseY = event.offsetY
    const autoScrollRight = middlePanel.offsetWidth - autoScrollThreshold * scale
    const autoScrollDown = middlePanel.offsetHeight - autoScrollThreshold * scale
    if (mouseX < autoScrollThreshold) {
      autoScrollX = -1
    }
    if (mouseX >= autoScrollRight) {
      autoScrollX = +1
    }
    if (mouseY < autoScrollThreshold) {
      autoScrollY = -1
    }
    if (mouseY >= autoScrollDown) {
      autoScrollY = +1
    }
  }

  hoverCanvasCoord = {
    x: ~~(event.offsetX / getScale()),
    y: ~~(event.offsetY / getScale()),
  }
})

tilesetCanvasContainer.addEventListener('mousemove', event => {
  if (mappoState.isLoading) {
    return
  }

  const scale = getScale()
  const tileset = mappoState.tileset
  const containerWidth = tilesetCanvasContainer.offsetWidth
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const hoverTileX = ~~(event.offsetX / (tileset.tileWidth * scale))
  const hoverTileY = ~~(event.offsetY / (tileset.tileHeight * scale))
  const hoverTileIndex = (hoverTileY * tilesetColumns) + hoverTileX

  mappoState.tilesetHoverTileX = hoverTileX
  mappoState.tilesetHoverTileY = hoverTileY
  mappoState.tilesetHoverIndex = hoverTileIndex
})

tilesetCanvasContainer.addEventListener('click', event => {
  if (mappoState.isLoading) {
    return
  }

  const scale = getScale()
  const tileset = mappoState.tileset
  const containerWidth = tilesetCanvasContainer.offsetWidth
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const selectedTileX = ~~(event.offsetX / (mappoState.tileset.tileWidth * scale))
  const selectedTileY = ~~(event.offsetY / (mappoState.tileset.tileHeight * scale))
  const selectedTileIndex = (selectedTileY * tilesetColumns) + selectedTileX

  mappoState.tilesetSelectedTileX = selectedTileX
  mappoState.tilesetSelectedTileY = selectedTileY
  mappoState.tilesetSelectedIndex = selectedTileIndex
})

middlePanel.addEventListener('mouseup', event => {
  mousedown = false
})

middlePanel.addEventListener('mouseenter', event => {
  mousein = true
})

middlePanel.addEventListener('mouseout', event => {
  mousein = false
  mappoState.cameraMoveX = 0
  mappoState.cameraMoveY = 0
  autoScrollX = 0
  autoScrollY = 0
  hoverCanvasCoord = null
})

const moveCamera = (moveX, moveY) => {
  mappoState.cameraX = clamp(mappoState.cameraX + moveX, 0, (mappoState.map.tileLayers[0].width * mappoState.tileset.tileWidth) - canvas.width)
  mappoState.cameraY = clamp(mappoState.cameraY + moveY, 0, (mappoState.map.tileLayers[0].height * mappoState.tileset.tileHeight) - canvas.height)
}

const clearCanvas = (canvas) => {
  const context = canvas.getContext('2d')
  context.globalCompositeOperation = 'source-over'
  context.fillStyle = checkerboardPattern
  context.fillRect(0, 0, canvas.width, canvas.height)
}

const getTilesetColumns = ({tileset, containerWidth}) => {
  const scaledTileWidth = tileset.tileWidth * getScale()
  return ~~(containerWidth / scaledTileWidth)
}

const getTilesetRows = ({tileset, containerWidth}) => {
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const roundedUpRows = ~~((tileset.numTiles + (tilesetColumns - 1)) / tilesetColumns)
  return roundedUpRows
}

const tick = () => {
  clearCanvas(canvas)
  clearCanvas(tilesetCanvas)
  clearCanvas(tilesetSelectedTileCanvas)
  clearCanvas(tilesetHoveringTileCanvas)

  if (!mappoState.isLoading) {
    const tileset = mappoState.tileset
    const tileWidth = tileset.tileWidth
    const tileHeight = tileset.tileHeight
    const containerWidth = tilesetCanvasContainer.offsetWidth

    mappoState.map.mapLayerOrder.forEach(layerIndex => {
      const tileLayer = mappoState.map.tileLayers[layerIndex]
      if (!tileLayer.isHidden) {
        renderLayer({
          tileset,
          layer: tileLayer,
          x: mappoState.cameraX * tileLayer.parallax.x,
          y: mappoState.cameraY * tileLayer.parallax.y,
          transparent: layerIndex > 0,
        })
      }
    })

    if (hoverCanvasCoord) {
      renderTileHighlightInvertedOutline({
        context,
        x: hoverCanvasCoord.x - ((~~mappoState.cameraX + hoverCanvasCoord.x) % tileWidth),
        y: hoverCanvasCoord.y - ((~~mappoState.cameraY + hoverCanvasCoord.y) % tileHeight),
        width: tileWidth,
        height: tileHeight,
      })
    }

    renderTileset({
      context: tilesetContext,
      tileset: mappoState.tileset,
      tilesetColumns: getTilesetColumns({tileset, containerWidth})
    })
    renderTileHighlightInvertedSolid({
      context: tilesetContext,
      x: mappoState.tilesetHoverTileX * tileWidth,
      y: mappoState.tilesetHoverTileY * tileHeight,
      width: tileWidth,
      height: tileHeight,
    })
    renderTileHighlightColorOutline({
      context: tilesetContext,
      x: mappoState.tilesetSelectedTileX * tileWidth,
      y: mappoState.tilesetSelectedTileY * tileHeight,
      color: 'white',
      width: tileWidth,
      height: tileHeight,
    })

    renderTile({
      context: tilesetSelectedTileContext,
      tileset: mappoState.tileset,
      tileIndex: mappoState.tilesetSelectedIndex,
      x: 0,
      y: 0,
    })
    renderTile({
      context: tilesetHoveringTileContext,
      tileset: mappoState.tileset,
      tileIndex: mappoState.tilesetHoverIndex,
      x: 0,
      y: 0,
    })
    tilesetSelectedTileIndex.innerText = mappoState.tilesetSelectedIndex
    tilesetHoveringTileIndex.innerText = mappoState.tilesetHoverIndex

    mappoState.cameraMoveX = 0
    mappoState.cameraMoveY = 0;
    (keyboard.isPressed(keyboard.KEYCODE_UP) || autoScrollY < 0) && (mappoState.cameraMoveY = -mappoState.cameraScrollAmount);
    (keyboard.isPressed(keyboard.KEYCODE_DOWN) || autoScrollY > 0) && (mappoState.cameraMoveY = +mappoState.cameraScrollAmount);
    (keyboard.isPressed(keyboard.KEYCODE_LEFT) || autoScrollX < 0) && (mappoState.cameraMoveX = -mappoState.cameraScrollAmount);
    (keyboard.isPressed(keyboard.KEYCODE_RIGHT) || autoScrollX > 0) && (mappoState.cameraMoveX = +mappoState.cameraScrollAmount);
    moveCamera(mappoState.cameraMoveX, mappoState.cameraMoveY)

    // map zooming
    if (keyboard.isPressed('ctrlKey')) {
      const prevScaleIndex = mappoState.scaleIndex
      if (keyboard.isPressed(keyboard.KEYCODE_PLUS)) {
        keyboard.release(keyboard.KEYCODE_PLUS)
        mappoState.scaleIndex++
      }
      if (keyboard.isPressed(keyboard.KEYCODE_MINUS)) {
        keyboard.release(keyboard.KEYCODE_MINUS)
        mappoState.scaleIndex--
      }
      if (keyboard.isPressed(keyboard.KEYCODE_0)) {
        keyboard.release(keyboard.KEYCODE_0)
        mappoState.scaleIndex = DEFAULT_SCALE_INDEX
      }
      mappoState.scaleIndex = clamp(mappoState.scaleIndex, 0, viewportScales.length - 1)
      // TODO(chuck): redux-ify?
      if (prevScaleIndex !== mappoState.scaleIndex) {
        resizeCanvas()
      }
    }
  }

  window.requestAnimationFrame(tick)
}

const resizeCanvas = () => {
  canvas.width = ~~((middlePanel.offsetWidth + (getScale() - 1)) / getScale())
  canvas.height = ~~((middlePanel.offsetHeight + (getScale() - 1)) / getScale())
  canvas.style.width = middlePanel.offsetWidth + 'px'
  canvas.style.height = middlePanel.offsetHeight + 'px'

  if (!mappoState.isLoading) {
    const containerWidth = tilesetCanvasContainer.offsetWidth
    const tileset = mappoState.tileset
    tilesetCanvas.width = getTilesetColumns({tileset, containerWidth}) * tileset.tileWidth
    tilesetCanvas.height = getTilesetRows({tileset, containerWidth}) * tileset.tileHeight
    tilesetCanvas.style.width = (tilesetCanvas.width * getScale()) + 'px'
    tilesetCanvas.style.height = (tilesetCanvas.height * getScale()) + 'px'
  }
}

window.addEventListener('resize', resizeCanvas)
resizeCanvas()

tick()

<commit_msg>simplify<commit_after>"use strict"

const asset = require('./asset')
const colorDepth = require('./converter/colorDepth')
const clamp = require('lodash/clamp')
const glob = require('glob')
const createMappoSession = require('./createMappoSession')
const createMappoMap = require('./createMappoMap')
const createMappoTileset = require('./createMappoTileset')
const detectFormat = require('./detectFormat')
const path = require('path')
const fs = require('fs')
const setupKeyboard = require('./setupKeyboard')
const renderTileHighlightInvertedOutline = require('./renderTileHighlightInvertedOutline')
const renderTileHighlightInvertedSolid = require('./renderTileHighlightInvertedSolid')
const renderTileHighlightColorOutline = require('./renderTileHighlightColorOutline')
const renderTile = require('./renderTile')
const renderLayer = require('./renderLayer')
const renderTileset = require('./renderTileset')
const createCheckerboardPattern = require('./createCheckerboardPattern')

// DOM REFERENCES
const pageTitle = document.querySelector('title')
const mapList = document.querySelector('.map-list')
const layerList = document.querySelector('.layer-list')
const canvas = document.querySelector('.mappo-viewport')
const context = canvas.getContext('2d')
const tilesetCanvasContainer = document.querySelector('.tileset-canvas-container')
const tilesetCanvas = document.querySelector('.tileset-canvas')
const tilesetContext = tilesetCanvas.getContext('2d')
const tilesetSelectedTileCanvas = document.querySelector('.tileset-selected-tile-canvas')
const tilesetSelectedTileContext = tilesetSelectedTileCanvas.getContext('2d')
const tilesetSelectedTileIndex = document.querySelector('.tileset-selected-tile-index')
const tilesetHoveringTileCanvas = document.querySelector('.tileset-hovering-tile-canvas')
const tilesetHoveringTileContext = tilesetHoveringTileCanvas.getContext('2d')
const tilesetHoveringTileIndex = document.querySelector('.tileset-hovering-tile-index')
const middlePanel = document.querySelector('.middle-panel')

const checkerboardPattern = createCheckerboardPattern({document})

const launchFolder = 'data' // TODO(chuck): temp hack for windows. empty string dunna work
console.log('launchFolder', launchFolder)
const mapGlob = '**/*.map'
const mapFilenames = glob.sync(mapGlob, {nocase: true})
const mappoSession = createMappoSession({
  fileSystem: {
    files: mapFilenames
  },
  launchFolder
})

const viewportScales = [0.25, 0.5, 0.75, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const DEFAULT_SCALE_INDEX = 4
const defaultMappoState = {
  scaleIndex: DEFAULT_SCALE_INDEX,
  isLoading: true,
  map: null,
  mapLayerOrder: null,
  mapLayerSelected: null,
  tileset: null,
  tilesetTileHovering: null,
  tilesetTileSelected: null,
  cameraX: 0,
  cameraY: 0,
  cameraMoveX: 0,
  cameraMoveY: 0,
  cameraScrollAmount: 1,
  keyPressed: {},
}
let mappoState = Object.assign({}, defaultMappoState)

const getScale = () => viewportScales[mappoState.scaleIndex]

mappoSession.getMapFilenames().forEach(mapFilename => {
  const li = document.createElement('li')
  li.setAttribute('title', mapFilename)
  li.innerText = mapFilename
  // TODO(chuck): temp hack for windows. figure out better launchFolder shenanigans
  li.addEventListener('click', event => {
    loadMap('data/' + mapFilename)
    pageTitle.innerText = 'Mappo - ' + mapFilename
  })
  mapList.appendChild(li)
})

const refreshMapLayerList = () => {
  layerList.innerHTML = ''
  mappoState.map.tileLayers.forEach((layer, index) => {
    const li = document.createElement('li')
    li.setAttribute('title', layer.description)
    li.innerText = layer.description
    li.classList.add('layer-list-item')
    if (mappoState.mapLayerSelected === layer) {
      li.classList.add('selected')
    }
    li.addEventListener('click', event => {
      // TODO(chuck): use a proper element?
      if (event.offsetX < 35) {
        li.classList.toggle('is-layer-hidden')
        layer.isHidden = !layer.isHidden
      }
      mappoState.mapLayerSelected = layer
      const layerListItems = document.querySelectorAll('.layer-list .layer-list-item')
      layerListItems.forEach(layerListItem => {
        layerListItem.classList.remove('selected')
      })
      li.classList.add('selected')
    })
    layerList.appendChild(li)
  })
}

const loadMap = mapFilename => {
  console.group()
  try {
    mappoState = Object.assign({}, defaultMappoState)
    mappoState.isLoading = true

    mappoState.cameraX = 0
    mappoState.cameraY = 0

    const mapBuffer = fs.readFileSync(mapFilename)
    const mapFormat = detectFormat(mapBuffer)
    if (!mapFormat.includes('map')) {
      console.error('expected map but got', mapFormat)
      return
    }
    console.log('mapFormat', mapFormat)
    let mapData
    try {
      mapData = asset.fromBuffer(mapBuffer, asset[mapFormat])
    } catch (exception) {
      console.log(exception)
      return
    }
    mappoState.map = createMappoMap({map: mapData})
    console.log(mapFilename, mapData)
    mappoState.mapLayerSelected = mappoState.map.tileLayers[mappoState.map.mapLayerOrder[0]]
    refreshMapLayerList()

    const vspFilename = path.join(
      path.dirname(mapFilename),
      mappoState.map.tilesetFilename
    )
    const vspBuffer = fs.readFileSync(vspFilename)
    const vspFormat = detectFormat(vspBuffer)
    console.log('vspFormat', vspFormat)
    let vspData
    try {
      vspData = asset.fromBuffer(vspBuffer, asset[vspFormat])
    } catch (exception) {
      console.log(exception)
      return
    }
    mappoState.tileset = createMappoTileset({context, tileset: vspData})
    console.log(vspFilename, vspData)
    tilesetSelectedTileCanvas.width = mappoState.tileset.tileWidth
    tilesetSelectedTileCanvas.height = mappoState.tileset.tileHeight
    tilesetHoveringTileCanvas.width = mappoState.tileset.tileWidth
    tilesetHoveringTileCanvas.height = mappoState.tileset.tileHeight

    mappoState.tileset.imageBitmapPromise.then(() => {
      mappoState.isLoading = false
      resizeCanvas()
    })
  } catch (exception) {
    console.error('ack!', exception)
  } finally {
    console.groupEnd()
  }
}

const keyboard = setupKeyboard({
  addEventListener: document.addEventListener,
  keyPressed: mappoState.keyPressed,
})

let mousedown = false
let mousein = false
middlePanel.addEventListener('mousedown', event => {
  mousedown = true
})

let hoverCanvasCoord = null
let autoScrollX = 0
let autoScrollY = 0
middlePanel.addEventListener('mousemove', event => {
  if (mappoState.isLoading) {
    return
  }

  const scale = getScale()
  if (mousedown) {
    moveCamera(
      -event.movementX / scale,
      -event.movementY / scale
    )
  }

  autoScrollX = 0
  autoScrollY = 0
  const autoScrollThreshold = (mappoState.tileset.tileWidth + mappoState.tileset.tileHeight) / 2;
  if (mousein) {
    const mouseX = event.offsetX
    const mouseY = event.offsetY
    const autoScrollRight = middlePanel.offsetWidth - autoScrollThreshold * scale
    const autoScrollDown = middlePanel.offsetHeight - autoScrollThreshold * scale
    if (mouseX < autoScrollThreshold) {
      autoScrollX = -1
    }
    if (mouseX >= autoScrollRight) {
      autoScrollX = +1
    }
    if (mouseY < autoScrollThreshold) {
      autoScrollY = -1
    }
    if (mouseY >= autoScrollDown) {
      autoScrollY = +1
    }
  }

  hoverCanvasCoord = {
    x: ~~(event.offsetX / getScale()),
    y: ~~(event.offsetY / getScale()),
  }
})

const getTileCoordAndIndex = ({
  tileset,
  containerWidth,
  pixelX,
  pixelY,
}) => {
  const scale = getScale()
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const tileX = ~~(pixelX / (tileset.tileWidth * scale))
  const tileY = ~~(pixelY / (tileset.tileHeight * scale))
  const tileIndex = (hoverTileY * tilesetColumns) + hoverTileX

  return {
    tileX,
    tileY,
    tileIndex,
  }
}

tilesetCanvasContainer.addEventListener('mousemove', event => {
  if (mappoState.isLoading) {
    return
  }

  mappoState.tilesetTileHovering = getTileCoordAndIndex({
    tileset: mappoState.tileset,
    containerWidth: tilesetCanvasContainer.offsetWidth,
    pixelX: event.offsetX,
    pixelY: event.offsetY,
  })
})

tilesetCanvasContainer.addEventListener('click', event => {
  if (mappoState.isLoading) {
    return
  }

  mappoState.tilesetTileSelected = getTileCoordAndIndex({
    tileset: mappoState.tileset,
    containerWidth: tilesetCanvasContainer.offsetWidth,
    pixelX: event.offsetX,
    pixelY: event.offsetY,
  })
})

middlePanel.addEventListener('mouseup', event => {
  mousedown = false
})

middlePanel.addEventListener('mouseenter', event => {
  mousein = true
})

middlePanel.addEventListener('mouseout', event => {
  mousein = false
  mappoState.cameraMoveX = 0
  mappoState.cameraMoveY = 0
  autoScrollX = 0
  autoScrollY = 0
  hoverCanvasCoord = null
})

const moveCamera = (moveX, moveY) => {
  mappoState.cameraX = clamp(mappoState.cameraX + moveX, 0, (mappoState.map.tileLayers[0].width * mappoState.tileset.tileWidth) - canvas.width)
  mappoState.cameraY = clamp(mappoState.cameraY + moveY, 0, (mappoState.map.tileLayers[0].height * mappoState.tileset.tileHeight) - canvas.height)
}

const clearCanvas = (canvas) => {
  const context = canvas.getContext('2d')
  context.globalCompositeOperation = 'source-over'
  context.fillStyle = checkerboardPattern
  context.fillRect(0, 0, canvas.width, canvas.height)
}

const getTilesetColumns = ({tileset, containerWidth}) => {
  const scaledTileWidth = tileset.tileWidth * getScale()
  return ~~(containerWidth / scaledTileWidth)
}

const getTilesetRows = ({tileset, containerWidth}) => {
  const tilesetColumns = getTilesetColumns({tileset, containerWidth})
  const roundedUpRows = ~~((tileset.numTiles + (tilesetColumns - 1)) / tilesetColumns)
  return roundedUpRows
}

const tick = () => {
  clearCanvas(canvas)
  clearCanvas(tilesetCanvas)
  clearCanvas(tilesetSelectedTileCanvas)
  clearCanvas(tilesetHoveringTileCanvas)

  if (!mappoState.isLoading) {
    const tileset = mappoState.tileset
    const tileWidth = tileset.tileWidth
    const tileHeight = tileset.tileHeight
    const containerWidth = tilesetCanvasContainer.offsetWidth

    mappoState.map.mapLayerOrder.forEach(layerIndex => {
      const tileLayer = mappoState.map.tileLayers[layerIndex]
      if (!tileLayer.isHidden) {
        renderLayer({
          tileset,
          layer: tileLayer,
          x: mappoState.cameraX * tileLayer.parallax.x,
          y: mappoState.cameraY * tileLayer.parallax.y,
          transparent: layerIndex > 0,
        })
      }
    })

    if (hoverCanvasCoord) {
      renderTileHighlightInvertedOutline({
        context,
        x: hoverCanvasCoord.x - ((~~mappoState.cameraX + hoverCanvasCoord.x) % tileWidth),
        y: hoverCanvasCoord.y - ((~~mappoState.cameraY + hoverCanvasCoord.y) % tileHeight),
        width: tileWidth,
        height: tileHeight,
      })
    }

    renderTileset({
      context: tilesetContext,
      tileset: mappoState.tileset,
      tilesetColumns: getTilesetColumns({tileset, containerWidth})
    })

    if (mappoState.tilesetTileHovering) {
      renderTileHighlightInvertedSolid({
        context: tilesetContext,
        x: mappoState.tilesetTileHovering.tileX * tileWidth,
        y: mappoState.tilesetTileHovering.tileY * tileHeight,
        width: tileWidth,
        height: tileHeight,
      })
      renderTile({
        context: tilesetHoveringTileContext,
        tileset: mappoState.tileset,
        tileIndex: mappoState.tilesetTileHovering.tileIndex,
        x: 0,
        y: 0,
      })

      tilesetHoveringTileIndex.innerText = mappoState.tilesetTileHovering.tileIndex
    }

    if (mappoState.tilesetTileSelected) {
      renderTileHighlightColorOutline({
        context: tilesetContext,
        x: mappoState.tilesetTileSelected.tileX * tileWidth,
        y: mappoState.tilesetTileSelected.tileY * tileHeight,
        color: 'white',
        width: tileWidth,
        height: tileHeight,
      })
      renderTile({
        context: tilesetSelectedTileContext,
        tileset: mappoState.tileset,
        tileIndex: mappoState.tilesetTileSelected.tileIndex,
        x: 0,
        y: 0,
      })

      tilesetSelectedTileIndex.innerText = mappoState.tilesetTileSelected.tileIndex
    }

    mappoState.cameraMoveX = 0
    mappoState.cameraMoveY = 0;
    (keyboard.isPressed(keyboard.KEYCODE_UP) || autoScrollY < 0) && (mappoState.cameraMoveY = -mappoState.cameraScrollAmount);
    (keyboard.isPressed(keyboard.KEYCODE_DOWN) || autoScrollY > 0) && (mappoState.cameraMoveY = +mappoState.cameraScrollAmount);
    (keyboard.isPressed(keyboard.KEYCODE_LEFT) || autoScrollX < 0) && (mappoState.cameraMoveX = -mappoState.cameraScrollAmount);
    (keyboard.isPressed(keyboard.KEYCODE_RIGHT) || autoScrollX > 0) && (mappoState.cameraMoveX = +mappoState.cameraScrollAmount);
    moveCamera(mappoState.cameraMoveX, mappoState.cameraMoveY)

    // map zooming
    if (keyboard.isPressed('ctrlKey')) {
      const prevScaleIndex = mappoState.scaleIndex
      if (keyboard.isPressed(keyboard.KEYCODE_PLUS)) {
        keyboard.release(keyboard.KEYCODE_PLUS)
        mappoState.scaleIndex++
      }
      if (keyboard.isPressed(keyboard.KEYCODE_MINUS)) {
        keyboard.release(keyboard.KEYCODE_MINUS)
        mappoState.scaleIndex--
      }
      if (keyboard.isPressed(keyboard.KEYCODE_0)) {
        keyboard.release(keyboard.KEYCODE_0)
        mappoState.scaleIndex = DEFAULT_SCALE_INDEX
      }
      mappoState.scaleIndex = clamp(mappoState.scaleIndex, 0, viewportScales.length - 1)
      // TODO(chuck): redux-ify?
      if (prevScaleIndex !== mappoState.scaleIndex) {
        resizeCanvas()
      }
    }
  }

  window.requestAnimationFrame(tick)
}

const resizeCanvas = () => {
  canvas.width = ~~((middlePanel.offsetWidth + (getScale() - 1)) / getScale())
  canvas.height = ~~((middlePanel.offsetHeight + (getScale() - 1)) / getScale())
  canvas.style.width = middlePanel.offsetWidth + 'px'
  canvas.style.height = middlePanel.offsetHeight + 'px'

  if (!mappoState.isLoading) {
    const containerWidth = tilesetCanvasContainer.offsetWidth
    const tileset = mappoState.tileset
    tilesetCanvas.width = getTilesetColumns({tileset, containerWidth}) * tileset.tileWidth
    tilesetCanvas.height = getTilesetRows({tileset, containerWidth}) * tileset.tileHeight
    tilesetCanvas.style.width = (tilesetCanvas.width * getScale()) + 'px'
    tilesetCanvas.style.height = (tilesetCanvas.height * getScale()) + 'px'
  }
}

window.addEventListener('resize', resizeCanvas)
resizeCanvas()

tick()

<|endoftext|>


<commit_before>(function ($) {

    // attach this new method to jQuery
    $.fn.extend({

        autoCompleteSelect: function (options) {

            var settings = $.extend({

                useRemote: true,
                dataUrl: "",
                minLength: 2,
                placeholder: ""

            }, options); // end of the extend

            return this.each(function (index, item) {

                // the multiselect with the selected values
                var $multiselect = $(item);
                $multiselect.hide();

                // the container that displays to the user
                var $container = $("<div>").addClass("ac-container").addClass("ac-gradientbackground");

                // setup the search box and associated controls
                setupSearch($container, $multiselect);

                // setup the container that houses the selected items

                // insert the container after the original multiselect
                $container.insertAfter($multiselect);

            });

            // initializes all the controls
            function setupSearch($container, $multiselect) {

                // create the controls
                var $searchContainer = $("<div>").addClass("ac-searchContainer");
                var $searchBox = $("<input>").addClass("ac-searchBox").attr("placeholder", settings.placeholder);
                var $loadingIcon = $("<div>").html(" ").addClass("ac-loading").hide();
                var $selectedContainer = $("<div>").addClass("ac-selectedContainer");

                // append the controls
                $searchContainer.append($searchBox);
                $searchContainer.append($loadingIcon);
                $container.append($searchContainer);
                $container.append($selectedContainer);

                $container.append($("<div>").css("clear", "both"));

                setupSelected($multiselect, $selectedContainer);

                // rely on remote call to populate
                if (settings.useRemote) {
                    attachJsonAutoComplete($searchBox, $loadingIcon, $selectedContainer, $multiselect);
                }
                // rely on data inside select list
                else {
                    attachLocalAutoComplete($searchBox, $loadingIcon, $selectedContainer, $multiselect);
                }

            }

            // attach the autocomplete into the textbox
            function attachJsonAutoComplete($searchBox, $loadingIcon, $selectedContainer, $multiselect) {

                $searchBox.autocomplete({

                    minLength: settings.minLength,
                    source: function (request, response) {

                        // show the loading icon
                        $loadingIcon.show();
                        $loadingIcon.css("display", "inline-block");

                        // get the values already selected

                        var selected = $multiselect.find("option:selected").map(function () { return $(this).val(); }).get();

                        $.getJSON(settings.dataUrl, { searchTerm: request.term, selected: selected }, function (data) {

                            // set the values to display
                            response($.map(data, function (item, index) { return { label: item.Label, value: item.Id }; }));

                            // hide the loading icon
                            $loadingIcon.hide();

                        });

                    },
                    select: function (event, ui) {

                        addSelected($selectedContainer, $multiselect, ui.item.value, ui.item.label);

                    },
                    close: function (event, ui) {

                        $searchBox.val("");

                    }

                });

            }

            // attach the auto complete without the need for a remote data source
            function attachLocalAutoComplete($searchBox, $loadingIcon, $selectedContainer, $multiselect) {

                // build the list of available
                var options = $multiselect.find("option(:not(:selected))");
                var available = $.map(options, function (item, index) { return { label: $(item).html(), value: $(item).val()} });

                $searchBox.autocomplete({

                    source: available,
                    select: function (event, ui) {

                        addSelected($selectedContainer, $multiselect, ui.item.value, ui.item.label);

                    },
                    close: function (event, ui) {

                        $searchBox.val("");

                    }
                });
            }

            // setup the already selected on population
            function setupSelected($multiselect, $container) {

                $.each($multiselect.find("option:selected"), function (index, item) {

                    addSelected($container, $multiselect, $(item).val(), $(item).html());

                });

            }

            // Add selected values into the display list
            function addSelected($container, $multiselect, id, label) {

                // create the objects
                var $selected = $("<span>").addClass("ac-selected").data("id", id).addClass("ac-gradientbackground");
                $selected.append($("<span>").html(label).css("display", "inline-block"));
                $selected.append($("<span>").addClass("ui-icon ui-icon-circle-close").css("margin", "-3px 4px").css("display", "inline-block"));
                $selected.data("id", id);

                // check if the option exists yet
                var $selectedOption = $multiselect.find('option[value="' + id + '"]');

                if ($selectedOption.length < 1) {
                    // add the value into the multiselect control
                    $multiselect.append($("<option>").val(id).html(label).attr("selected", ""));
                }
                else {
                    $selectedOption.attr("selected", "");
                }

                var $selectedDisplayObj = $container.children().filter(function (index) { return $(this).data("id") == id; });

                // do not add the object if it already exists
                if ($selectedDisplayObj.length < 1) {
                    // add to the selected container
                    $container.append($selected);

                    // attach a click event
                    $selected.click(function () {

                        // remove the object from the multiselect
                        $multiselect.find('option[value="' + id + '"]').removeAttr("selected");

                        // remove from the container
                        $(this).remove();

                    });
                }
            }

        } // end of the auto complete select

    }); // end of $.fn.extend

})(jQuery);<commit_msg>removed a space<commit_after>(function ($) {

    // attach this new method to jQuery
    $.fn.extend({

        autoCompleteSelect: function (options) {

            var settings = $.extend({

                useRemote: true,
                dataUrl: "",
                minLength: 2,
                placeholder: ""

            }, options); // end of the extend

            return this.each(function (index, item) {

                // the multiselect with the selected values
                var $multiselect = $(item);
                $multiselect.hide();

                // the container that displays to the user
                var $container = $("<div>").addClass("ac-container").addClass("ac-gradientbackground");

                // setup the search box and associated controls
                setupSearch($container, $multiselect);

                // setup the container that houses the selected items

                // insert the container after the original multiselect
                $container.insertAfter($multiselect);

            });

            // initializes all the controls
            function setupSearch($container, $multiselect) {

                // create the controls
                var $searchContainer = $("<div>").addClass("ac-searchContainer");
                var $searchBox = $("<input>").addClass("ac-searchBox").attr("placeholder", settings.placeholder);
                var $loadingIcon = $("<div>").html(" ").addClass("ac-loading").hide();
                var $selectedContainer = $("<div>").addClass("ac-selectedContainer");

                // append the controls
                $searchContainer.append($searchBox);
                $searchContainer.append($loadingIcon);
                $container.append($searchContainer);
                $container.append($selectedContainer);

                $container.append($("<div>").css("clear", "both"));

                setupSelected($multiselect, $selectedContainer);

                // rely on remote call to populate
                if (settings.useRemote) {
                    attachJsonAutoComplete($searchBox, $loadingIcon, $selectedContainer, $multiselect);
                }
                // rely on data inside select list
                else {
                    attachLocalAutoComplete($searchBox, $loadingIcon, $selectedContainer, $multiselect);
                }

            }

            // attach the autocomplete into the textbox
            function attachJsonAutoComplete($searchBox, $loadingIcon, $selectedContainer, $multiselect) {

                $searchBox.autocomplete({

                    minLength: settings.minLength,
                    source: function (request, response) {

                        // show the loading icon
                        $loadingIcon.show();
                        $loadingIcon.css("display", "inline-block");

                        // get the values already selected
                        var selected = $multiselect.find("option:selected").map(function () { return $(this).val(); }).get();

                        $.getJSON(settings.dataUrl, { searchTerm: request.term, selected: selected }, function (data) {

                            // set the values to display
                            response($.map(data, function (item, index) { return { label: item.Label, value: item.Id }; }));

                            // hide the loading icon
                            $loadingIcon.hide();

                        });

                    },
                    select: function (event, ui) {

                        addSelected($selectedContainer, $multiselect, ui.item.value, ui.item.label);

                    },
                    close: function (event, ui) {

                        $searchBox.val("");

                    }

                });

            }

            // attach the auto complete without the need for a remote data source
            function attachLocalAutoComplete($searchBox, $loadingIcon, $selectedContainer, $multiselect) {

                // build the list of available
                var options = $multiselect.find("option(:not(:selected))");
                var available = $.map(options, function (item, index) { return { label: $(item).html(), value: $(item).val()} });

                $searchBox.autocomplete({

                    source: available,
                    select: function (event, ui) {

                        addSelected($selectedContainer, $multiselect, ui.item.value, ui.item.label);

                    },
                    close: function (event, ui) {

                        $searchBox.val("");

                    }
                });
            }

            // setup the already selected on population
            function setupSelected($multiselect, $container) {

                $.each($multiselect.find("option:selected"), function (index, item) {

                    addSelected($container, $multiselect, $(item).val(), $(item).html());

                });

            }

            // Add selected values into the display list
            function addSelected($container, $multiselect, id, label) {

                // create the objects
                var $selected = $("<span>").addClass("ac-selected").data("id", id).addClass("ac-gradientbackground");
                $selected.append($("<span>").html(label).css("display", "inline-block"));
                $selected.append($("<span>").addClass("ui-icon ui-icon-circle-close").css("margin", "-3px 4px").css("display", "inline-block"));
                $selected.data("id", id);

                // check if the option exists yet
                var $selectedOption = $multiselect.find('option[value="' + id + '"]');

                if ($selectedOption.length < 1) {
                    // add the value into the multiselect control
                    $multiselect.append($("<option>").val(id).html(label).attr("selected", ""));
                }
                else {
                    $selectedOption.attr("selected", "");
                }

                var $selectedDisplayObj = $container.children().filter(function (index) { return $(this).data("id") == id; });

                // do not add the object if it already exists
                if ($selectedDisplayObj.length < 1) {
                    // add to the selected container
                    $container.append($selected);

                    // attach a click event
                    $selected.click(function () {

                        // remove the object from the multiselect
                        $multiselect.find('option[value="' + id + '"]').removeAttr("selected");

                        // remove from the container
                        $(this).remove();

                    });
                }
            }

        } // end of the auto complete select

    }); // end of $.fn.extend

})(jQuery);<|endoftext|>


<commit_before>var tower_Operate = {
	run: function(tower, thisRoom, attackDuration) {
		var thisTower = Game.getObjectById(tower);
		var towerRange = 70;

		var UnderAttackPos = Memory.roomsUnderAttack.indexOf(thisRoom.name);
		var salvagerPrepPos = Memory.roomsPrepSalvager.indexOf(thisRoom.name);
		if (salvagerPrepPos >= 0) {
			towerRange = 70;
		}
		if (UnderAttackPos >= 0 && thisTower.energy > 0) {
			var maxRange = 50;
			if (attackDuration >= 100) {
				maxRange = 10;
			} else if (attackDuration >= 50) {
				maxRange = 15;
			} else if (attackDuration >= 35) {
				maxRange = 20;
			} else if (attackDuration >= 20) {
				maxRange = 30;
			} else if (attackDuration >= 10) {
				maxRange = 40;
			}
			if (Memory.hasFired.indexOf(tower) > -1) {
				if (maxRange <= 15) {
					maxRange = maxRange + 10;
				} else {
					maxRange = maxRange + 5;
				}

				var towerPos = Memory.hasFired.indexOf(tower);
				if (towerPos >= 0) {
					Memory.hasFired.splice(towerPos, 1);
				}
			}
			var closestHostile = thisTower.pos.findInRange(FIND_HOSTILE_CREEPS, maxRange);
			if (closestHostile.length) {
				//Target healing creeps first
				closestHostile.sort(targetOther);
				//closestHostile.sort(targetHealer);
				if (closestHostile[0].getActiveBodyparts(HEAL) >= 4  && thisTower.pos.getRangeTo(closestHostile[0]) > 15 && closestHostile[0].hits == closestHostile[0].hitsMax) {
					//Probably a healer
					if (closestHostile[1] && (closestHostile[1].getActiveBodyparts(HEAL) < 2 || thisTower.pos.getRangeTo(closestHostile[1]) < 15)) {
						thisTower.attack(closestHostile[1]);
						Memory.hasFired.push(tower);
					} else if (thisTower.energy > (thisTower.energyCapacity * 0.5)) {
						var closestDamagedCreep = thisTower.pos.findClosestByRange(FIND_MY_CREEPS, {
							filter: (creep) => creep.hits < creep.hitsMax - 50
						});
						if (closestDamagedCreep) {
							thisTower.heal(closestDamagedCreep);
						}
					}
				} else {
					thisTower.attack(closestHostile[0]);
					Memory.hasFired.push(tower);
				}
			} else if (thisTower.energy > (thisTower.energyCapacity * 0.5)) {
				//Save 50% of the tower's energy to use on repelling attackers
				var closestDamagedCreep = thisTower.pos.findClosestByRange(FIND_MY_CREEPS, {
					filter: (creep) => creep.hits < creep.hitsMax - 50
				});
				if (closestDamagedCreep) {
					thisTower.heal(closestDamagedCreep);
				}
			}
		} else if ((thisTower.energy > (thisTower.energyCapacity * 0.5)) && (Game.time % 10 == 0)) {
			//Save 50% of the tower's energy to use on repelling attackers
			var closestDamagedCreep = thisTower.pos.findClosestByRange(FIND_MY_CREEPS, {
				filter: (creep) => creep.hits < creep.hitsMax - 150
			});
			if (closestDamagedCreep) {
				thisTower.heal(closestDamagedCreep);
			}
		}
		//Enable to see tower coverage
		//thisRoom.visual.rect(thisTower.pos.x - 15, thisTower.pos.y - 15, 30, 30, {fill: '#ff0019', opacity: 0.2});
		//wew
	}
};

module.exports = tower_Operate;

function targetHealer(a, b) {
	if (a.getActiveBodyparts(HEAL) < b.getActiveBodyparts(HEAL))
		return 1;
	if (a.getActiveBodyparts(HEAL) > b.getActiveBodyparts(HEAL))
		return -1;
	return 0;
}

function targetOther(a, b) {
	if ((a.getActiveBodyparts(HEAL) > b.getActiveBodyparts(HEAL)) && (a.hits > b.hits))
		return 1;
	if ((a.getActiveBodyparts(HEAL) > b.getActiveBodyparts(HEAL)) && (a.hits < b.hits))
		return 1;
	if ((a.getActiveBodyparts(HEAL) == b.getActiveBodyparts(HEAL)) && (a.hits > b.hits))
		return 1;
	if ((a.getActiveBodyparts(HEAL) == b.getActiveBodyparts(HEAL)) && (a.hits < b.hits))
		return -1;
	if ((a.getActiveBodyparts(HEAL) < b.getActiveBodyparts(HEAL)) && (a.hits > b.hits))
		return -1;
	if ((a.getActiveBodyparts(HEAL) < b.getActiveBodyparts(HEAL)) && (a.hits < b.hits))
		return -1;
	return 0;
}<commit_msg>Waste of energy<commit_after>var tower_Operate = {
	run: function(tower, thisRoom, attackDuration) {
		var thisTower = Game.getObjectById(tower);
		var towerRange = 70;

		var UnderAttackPos = Memory.roomsUnderAttack.indexOf(thisRoom.name);
		var salvagerPrepPos = Memory.roomsPrepSalvager.indexOf(thisRoom.name);
		if (salvagerPrepPos >= 0) {
			towerRange = 70;
		}
		if (UnderAttackPos >= 0 && thisTower.energy > 0) {
			var maxRange = 50;
			if (attackDuration >= 100) {
				maxRange = 10;
			} else if (attackDuration >= 50) {
				maxRange = 15;
			} else if (attackDuration >= 35) {
				maxRange = 20;
			} else if (attackDuration >= 20) {
				maxRange = 30;
			} else if (attackDuration >= 10) {
				maxRange = 40;
			}
			if (Memory.hasFired.indexOf(tower) > -1) {
				if (maxRange <= 15) {
					maxRange = maxRange + 10;
				} else {
					maxRange = maxRange + 5;
				}

				var towerPos = Memory.hasFired.indexOf(tower);
				if (towerPos >= 0) {
					Memory.hasFired.splice(towerPos, 1);
				}
			}
			var closestHostile = thisTower.pos.findInRange(FIND_HOSTILE_CREEPS, maxRange);
			if (closestHostile.length) {
				//Target healing creeps first
				closestHostile.sort(targetOther);
				//closestHostile.sort(targetHealer);
				if (closestHostile[0].getActiveBodyparts(HEAL) >= 2 && thisTower.pos.getRangeTo(closestHostile[0]) > 15 && closestHostile[0].hits == closestHostile[0].hitsMax) {
					//Probably a healer
					if (closestHostile[1] && (closestHostile[1].getActiveBodyparts(HEAL) < 2 || thisTower.pos.getRangeTo(closestHostile[1]) < 15)) {
						thisTower.attack(closestHostile[1]);
						Memory.hasFired.push(tower);
					} else if (thisTower.energy > (thisTower.energyCapacity * 0.5)) {
						var closestDamagedCreep = thisTower.pos.findClosestByRange(FIND_MY_CREEPS, {
							filter: (creep) => creep.hits < creep.hitsMax - 50
						});
						if (closestDamagedCreep) {
							thisTower.heal(closestDamagedCreep);
						}
					}
				} else {
					thisTower.attack(closestHostile[0]);
					Memory.hasFired.push(tower);
				}
			} else if (thisTower.energy > (thisTower.energyCapacity * 0.5)) {
				//Save 50% of the tower's energy to use on repelling attackers
				var closestDamagedCreep = thisTower.pos.findClosestByRange(FIND_MY_CREEPS, {
					filter: (creep) => creep.hits < creep.hitsMax - 50
				});
				if (closestDamagedCreep) {
					thisTower.heal(closestDamagedCreep);
				}
			}
		} else if ((thisTower.energy > (thisTower.energyCapacity * 0.5)) && (Game.time % 10 == 0)) {
			//Save 50% of the tower's energy to use on repelling attackers
			var closestDamagedCreep = thisTower.pos.findClosestByRange(FIND_MY_CREEPS, {
				filter: (creep) => creep.hits < creep.hitsMax - 150
			});
			if (closestDamagedCreep) {
				thisTower.heal(closestDamagedCreep);
			}
		}
		//Enable to see tower coverage
		//thisRoom.visual.rect(thisTower.pos.x - 15, thisTower.pos.y - 15, 30, 30, {fill: '#ff0019', opacity: 0.2});
		//wew
	}
};

module.exports = tower_Operate;

function targetHealer(a, b) {
	if (a.getActiveBodyparts(HEAL) < b.getActiveBodyparts(HEAL))
		return 1;
	if (a.getActiveBodyparts(HEAL) > b.getActiveBodyparts(HEAL))
		return -1;
	return 0;
}

function targetOther(a, b) {
	if ((a.getActiveBodyparts(HEAL) > b.getActiveBodyparts(HEAL)) && (a.hits > b.hits))
		return 1;
	if ((a.getActiveBodyparts(HEAL) > b.getActiveBodyparts(HEAL)) && (a.hits < b.hits))
		return 1;
	if ((a.getActiveBodyparts(HEAL) == b.getActiveBodyparts(HEAL)) && (a.hits > b.hits))
		return 1;
	if ((a.getActiveBodyparts(HEAL) == b.getActiveBodyparts(HEAL)) && (a.hits < b.hits))
		return -1;
	if ((a.getActiveBodyparts(HEAL) < b.getActiveBodyparts(HEAL)) && (a.hits > b.hits))
		return -1;
	if ((a.getActiveBodyparts(HEAL) < b.getActiveBodyparts(HEAL)) && (a.hits < b.hits))
		return -1;
	return 0;
}<|endoftext|>