1
0
Fork 0
mirror of https://github.com/Oreolek/undum.git synced 2024-05-17 00:18:17 +03:00

Removed mobile detection and progress bars

My goal is to make Undum more hackable, the JS code faster and more readable.

The mobile detection and progress bars are visual effects. A framework
doesn not need many of the visual effects and they should be done in CSS,
not in JS.

Also, useragent sniffing is bad.
This commit is contained in:
Alexander Yakovlev 2015-07-23 14:50:27 +07:00
parent 64b0a0c282
commit 1e16ea7be7
6 changed files with 416 additions and 666 deletions

View file

@ -38,6 +38,16 @@ scripting system with limited functionality. You can take control of
anything you want. Or you can just keep things simple using a bunch of
simple functions provided by Undum.
## What is the difference from the original Undum?
* This branch is rewritten on pure Javascript. You are no longer bound by jQuery.
Also, the code is more hackable now.
* The code uses Gulp as build system, making it even more hack-friendly.
* The code no longer thinks about CSS design. It should not think about whether the player is on mobile or not, it is a job for CSS. Also, animations are all CSS.
* No progress bars at the moment, so no animateQuality.
## Compatibility

File diff suppressed because one or more lines are too long

View file

@ -47,12 +47,6 @@ var hasLocalStorage = function() {
return hasStorage;
};
var isMobileDevice = function() {
return (navigator.userAgent.toLowerCase().search(
/iphone|ipad|palm|blackberry|android/
) >= 0 || document.querySelectorAll('html').offsetWidth <= 640);
};
/// Animations - you can totally redefine these! Fade in and fade out by default.
/// @param id string or object
var showBlock = function(id) {
@ -66,11 +60,24 @@ var showBlock = function(id) {
}
var hideBlock = function(id) {
var block = id; // typeof block === "element"
if (typeof id === "string") {
var block = document.getElementById(id);
}
if (typeof id === "element") {
var block = id;
if (typeof id === "object") { // probably NodeList
if (id.length == 0)
return;
Array.prototype.forEach.call(id, function(element, index) {
element.classList.add('hide');
element.classList.remove('show');
});
return;
}
if (typeof block.classList === "undefined")
{
console.log("Tried to hide an undefined block.");
console.log(id);
return;
}
block.classList.add('hide');
block.classList.remove('show');
@ -111,4 +118,3 @@ var extend = function(out) {
return out;
};

View file

@ -31,9 +31,6 @@ var character = null;
/* Tracks whether we're in interactive mode or batch mode. */
var interactive = true;
/* Tracks whether we're mobile or not. */
var mobile = isMobileDevice();
/* The system time when the game was initialized. */
var startTime;
@ -87,29 +84,28 @@ var parseFn = function(str) {
var loadHTMLSituations = function() {
var $htmlSituations = document.querySelectorAll("div.situation");
Array.prototype.forEach.call($htmlSituations, function($situation){
var id = $situation.getAttribute("id");
assert(game.situations[id] === undefined,
"existing_situation".l({id:id}));
var id = $situation.getAttribute("id");
assert(game.situations[id] === undefined, "existing_situation".l({id:id}));
var content = $situation.innerHTML;
var opts = {
var content = $situation.innerHTML;
var opts = {
// Situation content
optionText: $situation.getAttribute("data-option-text"),
canView: parseFn($situation.getAttribute("data-can-view")),
canChoose: parseFn($situation.getAttribute("data-can-choose")),
priority: parse($situation.getAttribute("data-priority")),
frequency: parse($situation.getAttribute("data-frequency")),
displayOrder: parse($situation.getAttribute("data-display-order")),
tags: parseList($situation.getAttribute("data-tags"), false),
// Simple Situation content.
heading: $situation.getAttribute("data-heading"),
choices: parseList($situation.getAttribute("data-choices"), true),
minChoices: parse($situation.getAttribute("data-min-choices")),
maxChoices: parse($situation.getAttribute("data-max-choices"))
};
optionText: $situation.getAttribute("data-option-text"),
canView: parseFn($situation.getAttribute("data-can-view")),
canChoose: parseFn($situation.getAttribute("data-can-choose")),
priority: parse($situation.getAttribute("data-priority")),
frequency: parse($situation.getAttribute("data-frequency")),
displayOrder: parse($situation.getAttribute("data-display-order")),
tags: parseList($situation.getAttribute("data-tags"), false),
// Simple Situation content.
heading: $situation.getAttribute("data-heading"),
choices: parseList($situation.getAttribute("data-choices"), true),
minChoices: parse($situation.getAttribute("data-min-choices")),
maxChoices: parse($situation.getAttribute("data-max-choices"))
};
game.situations[id] = new SimpleSituation(content, opts);
});
game.situations[id] = new SimpleSituation(content, opts);
});
};
@ -175,8 +171,8 @@ var showHighlight = function(domElement) {
}
showBlock(highlight);
setTimeout(function() {
hideBlock(highlight);
}, 2000);
hideBlock(highlight);
}, 2000);
};
/* Finds the correct location and inserts a particular DOM element
@ -308,8 +304,8 @@ var continueOutputTransaction = function() {
var endOutputTransaction = function() {
var scrollPoint = scrollStack.pop();
if (scrollStack.length === 0 && scrollPoint !== null) {
if (interactive && !mobile) {
$("body, html").animate({scrollTop: scrollPoint}, 500);
if (interactive) {
window.scroll(0,scrollPoint);
}
scrollPoint = null;
}
@ -421,33 +417,23 @@ var doTransitionTo = function(newSituationId) {
if (game.exit) {
game.exit(character, system, oldSituationId, newSituationId);
}
// Remove links and transient sections.
$('#content a').each(function(index, element) {
var a = $(element);
if (a.hasClass('sticky') || a.attr("href").match(/[?&]sticky[=&]?/))
return;
a.replaceWith($("<span>").addClass("ex_link").html(a.html()));
});
var contentToHide = $('#content .transient, #content ul.options');
contentToHide.add($("#content a").filter(function(){
return this.attr("href").match(/[?&]transient[=&]?/);
}));
if (interactive) {
if (mobile) {
contentToHide.fadeOut(2000);
} else {
contentToHide.
animate({opacity: 0}, 1500).
slideUp(500, function() {
$(this).remove();
});
}
} else {
contentToHide.remove();
}
}
// Remove links and transient sections.
var content = document.getElementById("content");
links = content.querySelectorAll("a");
Array.prototype.forEach.call(links, function(element, index) {
var a = element;
if (a.classList.contains('sticky') || a.getAttribute("href").match(/[?&]sticky[=&]?/))
return;
if (a.getAttribute("href").match(/[?&]transient[=&]?/)) {
hideBlock(a);
}
a.innerHTML = "<span class='ex_link'>"+a.innerHTML+"</span>";
});
hideBlock(content.querySelectorAll(".transient"));
hideBlock(content.querySelectorAll("ul.options"));
// Move the character.
current = newSituationId;
@ -473,26 +459,25 @@ var augmentLinks = function(content) {
output.innerHTML = content;
var links = output.querySelectorAll("a");
Array.prototype.forEach.call(links, function(element, index){
var href = element.getAttribute('href');
if (!element.classList.contains("raw")|| href.match(/[?&]raw[=&]?/)) {
var href = element.getAttribute('href');
if (!element.classList.contains("raw")|| href.match(/[?&]raw[=&]?/)) {
if (href.match(linkRe)) {
element.addEventListener("click", function(event) {
element.onclick = function(event) {
event.preventDefault();
// If we're a once-click, remove all matching
// links.
// If we're a once-click, remove all matching links.
if (element.classList.contains("once") || href.match(/[?&]once[=&]?/)) {
system.clearLinks(href);
system.clearLinks(href);
}
processClick(href);
return false;
});
};
} else {
element.classList.add("raw");
element.classList.add("raw");
}
}
});
}
});
return output.innerHTML;
};

View file

@ -74,11 +74,11 @@ ready(function() {
// Handle storage.
if (hasLocalStorage()) {
var erase = document.getElementById("erase");
erase.addEventListener("click", doErase());
erase.addEventListener("keydown", doErase());
erase.onclick = doErase;
erase.keydown = doErase;
var save = document.getElementById("save");
save.addEventListener("click", saveGame);
save.addEventListener("keydown", saveGame);
save.onclick = saveGame;
save.keydown = saveGame;
var storedCharacter = localStorage[getSaveId()];
if (storedCharacter) {
@ -95,23 +95,25 @@ ready(function() {
startGame();
}
} else {
$(".buttons").html("<p>"+"no_local_storage".l()+"</p>");
document.querySelector(".buttons").innerHTML = "<p>"+"no_local_storage".l()+"</p>";
startGame();
}
// Display the "click to begin" message. (We do this in code
// so that, if Javascript is off, it doesn't happen.)
document.getElementById("click_message").style.display = '';
showBlock("click_message");
// Show the game when we click on the title.
document.getElementById("title").addEventListener('click', function() {
// Note: if you do events with onclick, you have to have only one click event handler.
// You can use more complex methods if you expect to have more.
document.getElementById("title").onclick = function() {
showBlock("content")
showBlock("content_wrapper");
showBlock("legal");
showBlock("tools_wrapper");
document.getElementById("title").style.cursor = "default";
hideBlock("click_message");
});
};
/*
// Any point that an option list appears, its options are its

View file

@ -385,133 +385,10 @@ System.prototype.setQuality = function(quality, newValue) {
showHighlight(qualityBlock);
};
/* Changes a quality to a new value, but also shows a progress bar
* animation of the change. This probably only makes sense for
* qualities that are numeric, especially ones that the player is
* grinding to increase. The quality and newValue parameters are
* as for setQuality. The progress bar is controlled by the
* following options in the opts parameter:
*
* from - The proportion along the progress bar where the
* animation starts. Defaults to 0, valid range is 0-1.
*
* to - The proportion along the progress bar where the
* animation ends. Defaults to 1, valid range is 0-1.
*
* showValue - If true (the default) then the new value of the
* quality is displayed above the progress bar.
*
* displayValue - If this is given, and showValue is true, then
* the displayValue is used above the progress bar. If this
* isn't given, and showValue is true, then the display value
* will be calculated from the QualityDefinition, as
* normal. This option is useful for qualities that don't have
* a definition, because they don't normally appear in the UI.
*
* title - The title of the progress bar. If this is not given,
* then the title of the quality is used. As for displayValue
* this is primarily used when the progress bar doesn't have a
* QualityDefinition, and therefore doesn't have a title.
*
* leftLabel, rightLabel - Underneath the progress bar you can
* place two labels at the left and right extent of the
* track. These can help to give scale to the bar. So if the
* bar signifies going from 10.2 to 10.5, you might label the
* left and right extents with "10" and "11" respectively. If
* these are not given, then the labels will be omitted.
*/
/* Changes a quality to a new value, but also should show a progress bar
* animation of the change. Removed with the progress bar functionality. */
System.prototype.animateQuality = function(quality, newValue, opts) {
var currentValue = character.qualities[quality];
if (!currentValue) currentValue = 0;
// Change the base UI.
this.setQuality(quality, newValue);
if (!interactive) return;
// Overload default options.
var myOpts = {
from: 0,
to: 1,
title: null,
showValue: true,
displayValue: null,
leftLabel: null,
rightLabel: null
};
if (newValue < currentValue) {
myOpts.from = 1;
myOpts.to = 0;
}
extend(opts, myOpts);
// Run through the quality definition.
var qualityDefinition = game.qualities[quality];
if (qualityDefinition) {
// Work out how to display the value
if (myOpts.displayValue === null) {
myOpts.displayValue = qualityDefinition.format(character, newValue);
}
// Use the title.
if (myOpts.title === null) {
myOpts.title = qualityDefinition.title;
}
}
// Create the animated bar.
var totalWidth = 496;
var bar = document.getElementById("progress_bar").cloneNode(true);
bar.setAttribute("id", undefined);
var widthElement = bar.find("[data-attr='width']"); // TODO
widthElement.css('width', myOpts.from*totalWidth);
// Configure its labels
var titleLabel = bar.find("[data-attr='name']");
var valueLabel = bar.find("[data-attr='value']");
var leftLabel = bar.find("[data-attr='left_label']");
var rightLabel = bar.find("[data-attr='right_label']");
if (myOpts.title) {
titleLabel.html(myOpts.title);
} else {
titleLabel.remove();
}
if (myOpts.showValue && myOpts.displayValue !== null) {
valueLabel.html(myOpts.displayValue);
} else {
valueLabel.remove();
}
if (myOpts.leftLabel) {
leftLabel.html(myOpts.leftLabel);
} else {
leftLabel.remove();
}
if (myOpts.rightLabel) {
rightLabel.html(myOpts.rightLabel);
} else {
rightLabel.remove();
}
document.getElementById('content').appendChild(bar);
// Start the animation
setTimeout(function() {
widthElement.animate(
{'width': myOpts.to*totalWidth}, 1000,
function() {
// After a moment to allow the bar to be read, we can
// remove it.
setTimeout(function() {
if (mobile) {
bar.fadeOut(1500, function() {$(this).remove();});
} else {
bar.animate({opacity: 0}, 1500).
slideUp(500, function() {
$(this).remove();
});
}
}, 2000);
}
);
}, 500);
};
/* The character that is passed into each situation is of this