Clearing your cookies (specifically, localStorage) will delete your teams.Browsers sometimes randomly clear cookies - you should back up your teams or use the desktop client if you want to make sure you don\'t lose them.
';
+ buf += '
Clearing your cookies (specifically, localStorage) will delete your teams. ';
+ buf += 'Browsers sometimes randomly clear cookies - you should upload your teams to the Showdown database ';
+ buf += 'or make a backup yourself if you want to make sure you don\'t lose them.
';
buf += '';
buf += '
If you want to clear your cookies or localStorage, you can use the Backup/Restore feature to save your teams as text first.
';
var self = this;
@@ -848,6 +881,24 @@
this.exportMode = true;
this.update();
},
+ psExport: function () {
+ var cmd = '/teams ';
+ cmd += this.curTeam.teamid ? 'update' : 'save';
+ // teamName, formatid, rawPrivacy, rawTeam
+ var buf = [];
+ if (this.curTeam.teamid) buf.push(this.curTeam.teamid);
+ buf.push(this.curTeam.name);
+ buf.push(this.curTeam.format);
+ buf.push(this.$('input[name=teamprivacy]').get(0).checked ? 1 : 0);
+ var team = Storage.exportTeam(this.curSetList, this.curTeam.gen, false);
+ if (!team) return app.addPopupMessage("Add a Pokémon to your team before uploading it!");
+ buf.push(team);
+ app.send(cmd + " " + buf.join(', '));
+ this.exported = true;
+ $('button[name=psExport]').addClass('disabled');
+ $('button[name=psExport]')[0].disabled = true;
+ $('label[name=editMessage]').hide();
+ },
pokepasteExport: function (type) {
var team = Storage.exportTeam(this.curSetList, this.curTeam.gen, type === 'openteamsheet');
if (!team) return app.addPopupMessage("Add a Pokémon to your team before uploading it!");
@@ -966,6 +1017,10 @@
if (edited) {
Storage.saveTeam(team);
app.user.trigger('saveteams');
+ this.exported = false;
+ $('button[name=psExport]').removeClass('disabled');
+ $('button[name=psExport]')[0].disabled = false;
+ $('label[name=editMessage]').show();
}
// We're going to try to animate the team settling into its new position
@@ -1150,6 +1205,9 @@
if (/^gen\d+$/.test(formatName)) return true;
return false;
};
+ if (this.loadingTeam) buf += '
Downloading team from server... ';
+ buf += ' ';
if (exports.BattleFormats) {
buf += '
';
buf += '';
@@ -1180,6 +1238,10 @@
buf += '';
buf += '';
buf += '';
+ buf += '';
+ var privacy = (Storage.prefs('uploadprivacy') || typeof Storage.prefs('uploadprivacy') !== 'boolean') ? 'checked' : '';
+ buf += ' (Private: )';
+ buf += ' ';
buf += '';
if (this.curTeam.format.includes('vgc')) {
buf += '';
@@ -1473,6 +1535,9 @@
}
},
validate: function () {
+ if (this.curTeam.teamid && !this.curTeam.loaded) {
+ return app.loadTeam(this.curTeam, this.validate.bind(this));
+ }
var format = this.curTeam.format || 'gen7anythinggoes';
if (!this.curSetList.length) {
@@ -1483,8 +1548,9 @@
if (window.BattleFormats && BattleFormats[format] && BattleFormats[format].battleFormat) {
format = BattleFormats[format].battleFormat;
}
- app.sendTeam(this.curTeam);
- app.send('/vtm ' + format);
+ app.sendTeam(this.curTeam, function () {
+ app.send('/vtm ' + format);
+ });
},
teamNameChange: function (e) {
var name = ($.trim(e.currentTarget.value) || 'Untitled ' + (this.curTeamLoc + 1));
@@ -1496,6 +1562,11 @@
app.addPopupMessage("Names can't contain the character |, since they're used for storing teams.");
name = name.replace(/\|/g, '');
}
+ if (name.indexOf('[') >= 0 || name.indexOf(']') >= 0) {
+ app.addPopupMessage("Names can't contain the characters [ or ], since they're used for storing team IDs.");
+ name = name.replace(/\[/g, '');
+ name = name.replace(/\]/g, '');
+ }
this.curTeam.name = name;
e.currentTarget.value = name;
this.save();
diff --git a/js/client.js b/js/client.js
index 38093374b..cf60ed632 100644
--- a/js/client.js
+++ b/js/client.js
@@ -258,6 +258,7 @@ function toId() {
} else if (assertion.indexOf('\n') >= 0 || !assertion) {
app.addPopupMessage("Something is interfering with our connection to the login server.");
} else {
+ app.trigger('loggedin');
app.send('/trn ' + name + ',0,' + assertion);
}
},
@@ -553,6 +554,15 @@ function toId() {
self.addPopup(LoginPasswordPopup, {username: name, special: special});
});
+ this.on('loggedin', function () {
+ Storage.loadRemoteTeams(function () {
+ if (app.rooms.teambuilder) {
+ // if they have it open, be sure to update so it doesn't show 'no teams'
+ app.rooms.teambuilder.update();
+ }
+ });
+ });
+
this.on('response:savereplay', this.uploadReplay, this);
this.on('response:rooms', this.roomsResponse, this);
@@ -877,10 +887,11 @@ function toId() {
serializeForm: function (form, checkboxOnOff) {
// querySelector dates back to IE8 so we can use it
// fortunate, because form serialization is a HUGE MESS in older browsers
- var elements = form.querySelectorAll('input[name], select[name], textarea[name], keygen[name]');
+ var elements = form.querySelectorAll('input[name], select[name], textarea[name], keygen[name], button[value]');
var out = [];
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
+ if ($(element).attr('type') === 'submit') continue;
if (element.type === 'checkbox' && !element.value && checkboxOnOff) {
out.push([element.name, element.checked ? 'on' : 'off']);
} else if (!['checkbox', 'radio'].includes(element.type) || element.checked) {
@@ -909,16 +920,50 @@ function toId() {
e.stopPropagation();
}
},
+ loadingTeam: null,
+ loadingTeamQueue: [],
+ loadTeam: function (team, callback) {
+ if (!team.teamid) return;
+ if (!this.loadingTeam) {
+ var app = this;
+ this.loadingTeam = true;
+ $.get(app.user.getActionPHP(), {
+ act: 'getteam',
+ teamid: team.teamid,
+ }, Storage.safeJSON(function (data) {
+ app.loadingTeam = false;
+ if (data.actionerror) {
+ return app.addPopupMessage("Error loading team: " + data.actionerror);
+ }
+ team.privacy = data.privacy;
+ team.team = data.team;
+ team.loaded = true;
+ callback(team);
+ var entry = app.loadingTeamQueue.shift();
+ if (entry) {
+ app.loadTeam(entry[0], entry[1]);
+ }
+ }));
+ } else {
+ this.loadingTeamQueue.push([team, callback]);
+ }
+ },
/**
* Send team to sim server
*/
- sendTeam: function (team) {
+ sendTeam: function (team, callback) {
+ if (team && team.teamid && !team.loaded) {
+ return this.loadTeam(team, function (team) {
+ app.sendTeam(team, callback);
+ });
+ }
var packedTeam = '' + Storage.getPackedTeam(team);
if (packedTeam.length > 25 * 1024 - 6) {
alert("Your team is over 25 KB. Please use a smaller team.");
return;
}
this.send('/utm ' + packedTeam);
+ callback();
},
/**
* Receive from sim server
@@ -1256,6 +1301,9 @@ function toId() {
var columnChanged = false;
window.NonBattleGames = {rps: 'Rock Paper Scissors'};
+ for (var i = 3; i <= 9; i = i + 2) {
+ window.NonBattleGames['bestof' + i] = 'Best-of-' + i;
+ }
window.BattleFormats = {};
for (var j = 1; j < formatsList.length; j++) {
if (isSection) {
@@ -2110,12 +2158,15 @@ function toId() {
},
dispatchClickButton: function (e) {
var target = e.currentTarget;
- if (target.name) {
+ var type = $(target).attr('type');
+ if (type === 'submit') type = null;
+ if (target.name || type) {
app.dismissingSource = app.dismissPopups();
app.dispatchingButton = target;
e.preventDefault();
e.stopImmediatePropagation();
- this[target.name](target.value, target);
+ if (target.name && this[target.name]) this[target.name](target.value, target);
+ if (type && this[type]) this[type](target.value, target);
delete app.dismissingSource;
delete app.dispatchingButton;
}
@@ -2143,6 +2194,31 @@ function toId() {
//
},
+ /**
+ * Used for , does format popup and caches value in button value
+ */
+ selectformat: function (value, target) {
+ var format = value || 'gen9randombattle';
+ app.addPopup(FormatPopup, {format: format, sourceEl: target, selectType: 'watch', onselect: function (newFormat) {
+ target.value = newFormat;
+ }});
+ },
+
+ copyText: function (value, target) {
+ var dummyInput = document.createElement("input");
+ // This is a hack. You can only "select" an input field.
+ // The trick is to create a short lived input element and destroy it after a copy.
+ // (stolen from the replay code, obviously --mia)
+ dummyInput.id = "dummyInput";
+ dummyInput.value = value || target.value || target.href || "";
+ dummyInput.style.position = 'absolute';
+ target.appendChild(dummyInput);
+ dummyInput.select();
+ document.execCommand("copy");
+ target.removeChild(dummyInput);
+ $(target).text('Copied!');
+ },
+
// layout
bestWidth: 659,
diff --git a/js/storage.js b/js/storage.js
index ab798d267..30ccd2a54 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -585,6 +585,40 @@ Storage.loadTeams = function () {
} catch (e) {}
};
+Storage.loadRemoteTeams = function (after) {
+ $.get(app.user.getActionPHP(), {act: 'getteams'}, Storage.safeJSON(function (data) {
+ if (data.actionerror) {
+ return app.addPopupMessage('Error loading uploaded teams: ' + data.actionerror);
+ }
+ for (var i = 0; i < data.teams.length; i++) {
+ var team = data.teams[i];
+ var matched = false;
+ for (var j = 0; j < Storage.teams.length; j++) {
+ var curTeam = Storage.teams[j];
+ if (curTeam.teamid === team.teamid) {
+ // prioritize locally saved teams over remote
+ // as so to not overwrite changes
+ matched = true;
+ break;
+ }
+ }
+ team.loaded = false;
+ if (!matched) {
+ // hack so that it shows up in the format selector list
+ team.folder = '';
+ // team comes down from loginserver as comma-separated list of mons
+ // to save bandwidth
+ var mons = team.team.split(',').map(function (mon) {
+ return {species: mon};
+ });
+ team.team = Storage.packTeam(mons);
+ Storage.teams.unshift(team);
+ }
+ }
+ if (typeof after === 'function') after();
+ }));
+};
+
Storage.loadPackedTeams = function (buffer) {
try {
this.teams = Storage.unpackAllTeams(buffer);
@@ -669,16 +703,20 @@ Storage.unpackAllTeams = function (buffer) {
};
Storage.unpackLine = function (line) {
+ var leftBracketIndex = line.indexOf('[');
+ if (leftBracketIndex < 0) leftBracketIndex = 0;
var pipeIndex = line.indexOf('|');
if (pipeIndex < 0) return null;
+ if (leftBracketIndex > pipeIndex) leftBracketIndex = 0;
var bracketIndex = line.indexOf(']');
if (bracketIndex > pipeIndex) bracketIndex = -1;
var isBox = line.slice(0, bracketIndex).endsWith('-box');
var slashIndex = line.lastIndexOf('/', pipeIndex);
if (slashIndex < 0) slashIndex = bracketIndex; // line.slice(slashIndex + 1, pipeIndex) will be ''
- var format = bracketIndex > 0 ? line.slice(0, isBox ? bracketIndex - 4 : bracketIndex) : 'gen9';
+ var format = bracketIndex > 0 ? line.slice((leftBracketIndex ? leftBracketIndex + 1 : 0), isBox ? bracketIndex - 4 : bracketIndex) : 'gen9';
if (format && format.slice(0, 3) !== 'gen') format = 'gen6' + format;
return {
+ teamid: leftBracketIndex > 0 ? Number(line.slice(0, leftBracketIndex)) : undefined,
name: line.slice(slashIndex + 1, pipeIndex),
format: format,
gen: parseInt(format[3], 10) || 6,
@@ -691,7 +729,12 @@ Storage.unpackLine = function (line) {
Storage.packAllTeams = function (teams) {
return teams.map(function (team) {
- return (team.format ? '' + team.format + (team.capacity === 24 ? '-box]' : ']') : '') + (team.folder ? '' + team.folder + '/' : '') + team.name + '|' + Storage.getPackedTeam(team);
+ return (
+ (team.teamid ? '' + team.teamid + '[' : '') +
+ (team.format ? '' + team.format + (team.capacity === 24 ? '-box]' : ']') : '') +
+ (team.folder ? '' + team.folder + '/' : '') + team.name + '|' +
+ Storage.getPackedTeam(team)
+ );
}).join('\n');
};
diff --git a/replays/build b/replays/build
index e60cbc42f..b58d8a99e 100755
--- a/replays/build
+++ b/replays/build
@@ -15,7 +15,8 @@ function updateIndex() {
// add hashes to js and css files
process.stdout.write("Updating hashes... ");
- indexContents = indexContents.replace(/(src|href)="\/(.*?)(\?[a-z0-9]*?)?"/g, runReplace);
+ // Check for