// ==UserScript==
// @author @Chyld314 @DanielOndiordna
// @name IMATTC
// @version 1.12.4.20230113.110500
// @updateURL https://raw.githubusercontent.com/IITC-CE/Community-plugins/master/dist/DanielOnDiordna/imattc.meta.js
// @downloadURL https://raw.githubusercontent.com/IITC-CE/Community-plugins/master/dist/DanielOnDiordna/imattc.user.js
// @description Ingress Mission Authoring Tool Total Conversion, adding categories for missions, show banner preview, export json, download images, and more.
// @id imattc@DanielOnDiordna
// @category Mission Creator
// @match http://missions.ingress.com/
// @match http://missions.ingress.com/edit*
// @match https://missions.ingress.com/
// @match https://missions.ingress.com/edit*
// @match https://missions.ingress.com/about
// @grant none
// ==/UserScript==
// removed from the Tampermonkey headers:
// @require https://code.jquery.com/ui/1.10.4/jquery-ui.min.js
var imattcversion = "1.12.4.20230113.110500";
var changelog = `
Changelog:
version 1.12.4.20230113.110500
- hide the not-working submit button displayed with the checkboxes buttons (not implemented yet)
version 1.12.3.20230108.203000
- fixed Preview Images rows when using browser zoom
version 1.12.2.20220808.203900
- fixed category selector for new missions to remember category title instead of index
- hide the create new mission button when processing checked missions (it still flashes in view)
- added button counter for move button, disable when 0 checkboxes are checked
- fixed enabling checkbox buttons when using the Toggle all checkboxes button
version 1.12.1.20220807.224600
- added checkbox button to discard drafts of published missions
- added button counters when selecting missions with checkboxes, buttons disable when 0 checkboxes are checked
version 1.12.0.20220710.000300
- changed preview image formatting to match Prime spacing, reduced image download size (=s136)
version 1.11.5.20220613.235700
- checkboxes can now also be selected by clicking on the mission image, title and icon
- downloaded JSON filenames will now get the category name as a prefix text, and the timestamp at the end
- downloaded image filenames will now get the category name as a prefix text
- fixed the timestamp formatting
- imported category data (file or clipboard) is now checked for valid formatting first
- added a warning to reload and sign in again if the page shows a certain welcome message after some time of inactivity
- plugin source code indentation applied
version 1.11.4.20220610.193900
- moved the category selector at the preview screen for new missions next to the submit button instead of above
version 1.11.3.20220609.234400
- moved Category buttons into a Category drop down menu
- added Category menu options to import/export category data as a file
- added Category menu options to delete all category data
- fixed scroll location when using buttons to move a category up or down
- added a mission menu item Download Image for every single mission
version 1.11.2.20220608.183700
- fixed download json for Android Kiwi browser with Tampermonkey and iOS Safari with Userscripts extension
version 1.11.1.20220607.215600
- fixed the search button icon in edit mission screen by overriding the bootstrap .input-group-btn font-size
- fixed the mission preview showing as transparant
- increased font size for massive delete/unpublish/withdraw notification text
- added a cancel button while executing a massive delete/unpublish/withdraw
- removed footer with about link at the editor screens
- set and store the selected category when submitting a mission
version 1.11.0.20220606.235700
- increased plugin initialization speed, removed timers, replaced with page observer
- added powered by message at the login screen
- added about IMATTC message at the about screen and an about link at the footer of the missions screen
- added an Download JSON button for all missions in a category, compatible with UMM json files
- added an Download Images button to mass download separate files for each mission image in a category
- added a default mission type radio button for new missions
- added checkbox actions for massive delete/unpublish/withdraw actions
- changed mission preview map to 100% width
- swapped the Move up and Move down button positions, better for smaller screens
- replaced some jquery functions with non jquery methods to prevent console log errors at the log on screen
version 1.10.0.20220519.220900:
- added a Submit mission menu item for missions with Draft status
version 1.9.0.20220221.161700:
- created a modified version from source https://github.com/andyjennings314/IMATTC/blob/master/IMATTC.user.js (version 1.8.1)
- moved localstorage actions to functions
- added username to storage to support multiple user logins in same browser profile
- changed border size to 100% width and missions to 16.6% width (100/6) to fit 6 missions per row
- changed menu link "Add to Category" to "Move to Category"
- changed menu link "Remove from Category" to "Move to Unsorted Missions"
- fixed unsortedCollapse restore status, default to false
- fixed userscript headers to work under IITC-CE the Button extension
- removed header require jquery-ui.min.js
- fixed preview image title category name
- added checkboxes for massive move actions
- added store settings action when changing sort direction within a category
- changed mission width to 1/6 of the screen, to fit 6 in a row
- fixed minimum mission width for smaller screens (mobile)
- added display of mission counts per category
- added category move up/down buttons
- added button to sort categories by titles (inverts sort direction when pressed again)
`;
var about = `Version ${imattcversion}
Ingress Mission Authoring Tool Total Conversion (IMATTC).
This userscript does the following modifications (and more):
1. Show missions in categories. Drag drop missions into categories.
2. Preview Images as a banner in rows of 6 missions.
3. Preview Route for all missions in a category.
Additions by DanielOndiordna (and more):
1. Display missions in rows of maximum 6 missions.
2. Download JSON, compatible with Ultimate Mission Maker (UMM) plugin
3. Download Images as separate files
4. Use checkboxes to select and move missions into a category
5. Use checkboxes to mass Delete or Unpublish missions
6. Rename a category, move categories up and down.
7. Support multiple users with personal category storage
8. Adjust width of missions to fill the screen
9. Select a default mission type (auto select) for new missions
10. Option to skip the mission type selection screen
11. Fixed the delete missions from category process
12. Rename a category
13. Show category totals
Source (version 1.8.1): https://github.com/andyjennings314/IMATTC/blob/master/IMATTC.user.js
Forks: https://github.com/andyjennings314/IMATTC/network/members
Github fork by DanielOndiordna: https://github.com/DanielOndiordna/IMATTC
Latest updates by DanielOndiordna: https://softspot.nl/ingress/#iitc-plugin-imattc.user.js
`;
(function() {
// added to load the Jquery UI for Tampermonkey, Greasemonkey and the IITC-CE the Button extension:
if (!document.head.querySelector('script[src="https://code.jquery.com/ui/1.10.4/jquery-ui.min.js"]')) {
let insertJqUI = document.createElement('script');
insertJqUI.setAttribute("type", "text/javascript");
insertJqUI.setAttribute("src", "https://code.jquery.com/ui/1.10.4/jquery-ui.min.js");
document.head.appendChild(insertJqUI);
}
if (typeof window.$ != 'function') { // inject jquery only if missing
let insertJq = document.createElement('script');
insertJq.setAttribute("type", "text/javascript");
insertJq.setAttribute("src", "https://code.jquery.com/jquery-1.12.4.min.js");
insertJq.onload = function() {
window.$ = (window.jQuery || unsafeWindow.jQuery);
init();
}
document.head.appendChild(insertJq);
} else {
init();
}
})();
(function() {
'use strict';
//Latest version of Bootstrap, and correct version of jQuery
// $("link[href='vendor/bootstrap/css/bootstrap.css']").attr("href", "https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css");
// non jquery method:
let oldbootstraplink = document.head.querySelector('link[href="vendor/bootstrap/css/bootstrap.css"]');
if (oldbootstraplink) {
oldbootstraplink.setAttribute("href", "https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css");
}
//$('body').append('');
// Modify time conversion variables to ones with actual granularity
if (typeof TimeConversionConstants != 'undefined') {
TimeConversionConstants.MINUTE_GRANULARITY_MINUTES = 1;
TimeConversionConstants.HOUR_GRANULARITY_MINUTES = 15;
TimeConversionConstants.DAY_GRANULARITY_HOURS = 12;
}
//Build CSS rules
let newCssRules = "" // "";
// $("head").append(newCssRules);
// non jquery method:
let newCssRulesstylesheet = document.head.appendChild(document.createElement('style'));
newCssRulesstylesheet.innerHTML = newCssRules;
})();
function init() {
const w = typeof unsafeWindow === 'undefined' ? window : unsafeWindow;
function addaboutpagemessage() {
let aboutul = document.querySelector('#about-page #container ul');
if (!aboutul) return false;
let abouthtml = about.replace(/(http[^\n ]+)/g,"$1").replace(/\n/g," \n");
if (document.querySelector('.aboutimattc')) return true; // already defined
let li = document.createElement('li');
li.className = 'aboutimattc';
li.innerHTML = `IMATTC
${abouthtml}`;
let changelogbutton = li.appendChild(document.createElement('button'));
changelogbutton.textContent = 'Changelog';
changelogbutton.addEventListener('click',function(e) {
alert(changelog);
},false);
aboutul.prepend(li);
}
function addpoweredbymessage(appendtoelement) {
if (!appendtoelement) return false;
if (document.querySelector('.poweredbyimattc')) return true; // already defined
let div = appendtoelement.appendChild(document.createElement('div'));
div.className = 'poweredbyimattc';
div.style.color = "#489299";
div.style.fontStyle = "italic";
div.innerText = `Powered by IMATTC version ${imattcversion}`;
return true;
}
function addfooter() {
let footer = document.getElementById('footer');
if (footer) return;
footer = document.body.appendChild(document.createElement('div'));
footer.id = "footer";
footer.innerHTML = 'About';
}
function removefooter() {
let footer = document.getElementById('footer');
if (!footer) return;
footer.remove();
}
let currentpage = '';
function detectPageChanged() {
if (document.querySelector('#about-page') && currentpage != 'about') {
currentpage = 'about';
addaboutpagemessage();
return currentpage;
}
if (document.querySelector('.landing-page') && currentpage != 'landing') {
currentpage = 'landing';
addpoweredbymessage(document.querySelector('.landing-page'));
return currentpage;
}
if (document.querySelector('.container .editor') && currentpage != 'editor') {
currentpage = 'editor';
// console.log('editor-page');
setupAngular();
removefooter();
return currentpage;
}
if (document.querySelector('.container .missions-list') && currentpage != 'missions') {
currentpage = 'missions';
// console.log('missions-page');
setupAngular();
addfooter();
return currentpage;
}
return '';
}
// console.log('setup observer');
let bodyobserver;
function bodymutationcallback(mutations) {
if (detectPageChanged()) {
//console.log('page changed',currentpage);
}
}
bodyobserver = new MutationObserver(bodymutationcallback);
bodyobserver.observe(document.body, {
childList: true,
subtree: true
});
detectPageChanged();
let angularrunning = false;
function setupAngular() {
if (angularrunning) return;
angularrunning = true;
// console.log('find angular');
let trycnt = 0;
const initWatcher = setInterval(() => {
if (w.angular) {
trycnt++;
let err = false;
try {
initAngular();
} catch (error) {
clearInterval(initWatcher);
err = error;
console.log('FAILED: initAngular',error);
}
if (!err) {
try {
clearInterval(initWatcher);
pageChange();
} catch (error) {
err = error;
console.log('FAILED: pageChange',error);
}
if (!err) console.log('initAngular ready, tries:',trycnt);
}
}
}, 1000);
}
function initAngular() {
const el = w.document.querySelector("*[ng-app]");
w.$app = w.angular.element(el);
w.$injector = w.$app.injector();
w.$rootScope = w.$app.scope();
w.$filter = w.$app.injector().get('$filter');
w.$compile = w.$app.injector().get('$compile');
w.$location = w.$app.injector().get('$location');
w.$timeout = w.$app.injector().get('$timeout');
w.$q = w.$app.injector().get('$q');
w.$http = w.$app.injector().get('$http');
w.WireUtil = w.$app.injector().get('WireUtil');
w.Api = w.$app.injector().get('Api');
w.$rootScope.$on('$routeChangeStart', function(next, last) {
setTimeout(() => {
pageChange()
}, 500);
});
w.$scope = element => w.angular.element(element).scope();
}
function pageChange() {
var loadingElement = $('div.loading-screen');
var loadingScope = w.$scope(loadingElement);
if (!loadingScope.hasPendingRequests()) {
whenItsLoaded();
} else {
setTimeout(() => {
pageChange();
}, 250);
}
}
function clickButton2CheckedMissionsList(missionScope,lastonefailed) {
let resultsdiv = document.querySelector('.clickButton2results');
let cancelbutton = document.querySelector('.cancel-clickbutton2-button');
function restoreContainer(keepresultsdiv = false) {
if (!keepresultsdiv && resultsdiv) resultsdiv.remove();
if (cancelbutton) cancelbutton.remove();
if (document.querySelector('.container.ng-scope')) {
document.querySelector('.container.ng-scope').style.display = "unset"; // show everything
}
if (document.querySelector('.create-mission-button')) {
document.querySelector('.create-mission-button').style.display = 'unset'; // show the Create New Mission button
}
}
try {
if (localStorage.getItem('clickbutton2missionguids') == null || typeof localStorage.getItem('clickbutton2missionguids') != 'string' || localStorage.getItem('clickbutton2missionguids') == '') {
// nothing defined (anymore)
restoreContainer();
return false;
}
let missionguids = JSON.parse(localStorage.getItem('clickbutton2missionguids'));
if (!(missionguids instanceof Array) || missionguids.length <= 0) {
// empty list (last one handled)
localStorage.removeItem('clickbutton2missionguids');
restoreContainer();
if (lastonefailed) setTimeout(whenItsLoaded);
return false;
}
if (!resultsdiv) {
resultsdiv = document.createElement('div');
resultsdiv.className = 'clickButton2results';
if (document.querySelector('.navbar')) {
document.querySelector('.navbar').after(resultsdiv);
}
cancelbutton = document.createElement('button');
cancelbutton.className = "cancel-clickbutton2-button";
cancelbutton.style.marginLeft = '20px';
cancelbutton.textContent = "Cancel";
cancelbutton.addEventListener('click',function(e) {
console.log("Canceled checked missions, keep:",localStorage.getItem('clickbutton2missionguids'));
localStorage.setItem('clickbutton2missionguids',JSON.stringify([]));
if (document.querySelector('.container.ng-scope')) {
document.querySelector('.container.ng-scope').style.display = "unset"; // show everything
}
},false);
resultsdiv.after(cancelbutton);
}
if (document.querySelector('.container.ng-scope')) {
document.querySelector('.container.ng-scope').style.display = "none"; // hide everything
}
if (document.querySelector('.yellow create-mission-button')) {
document.querySelector('.yellow create-mission-button').style.display = "none"; // hide create button
}
let total = missionguids.length;
let missionguid = missionguids.shift();
localStorage.setItem('clickbutton2missionguids',JSON.stringify(missionguids));
resultsdiv.innerText = 'Checked ' + total + ' mission' + (total == 1?'':'s') + '...';
let checkedmissions = missionScope.missions.filter((el)=>{return el.mission_guid == missionguid;}); // expect 1 result
if (checkedmissions.length != 1) {
console.log("FAILED: mission guid not found, skipped",missionguid);
resultsdiv.innerText += ' mission guid not found, skipped';
setTimeout(function() {
clickButton2CheckedMissionsList(missionScope,true);
},1000);
return true;
}
let buttontitle = missionScope.missionListStates[checkedmissions[0].state].BUTTON2.title;
resultsdiv.innerText = buttontitle + ' ' + total + ' mission' + (total == 1?'':'s') + '...';
missionScope.button2Clicked(checkedmissions[0]);
return true;
} catch(e) {
console.log("FAILED: alter missions failed, cancel all");
resultsdiv.innerText = 'FAILED: alter missions failed, cancel all';
restoreContainer(true);
localStorage.removeItem('clickbutton2missionguids');
return false;
}
};
function whenItsLoaded() {
var missionScope = w.$scope($('div.list'));
if (missionScope) {
if (clickButton2CheckedMissionsList(missionScope)) return; // skip setup if clicked
missionListSetup(missionScope);
} else {
var editScope = w.$scope($('div.editor'));
if (editScope) {
missionEditSetup(editScope);
} else {
var previewScope = w.$scope($('div.preview-mission'));
if (previewScope){
missionPreviewSetup(previewScope);
}
}
}
}
function missionPreviewSetup(previewScope) {
//$('body').css('margin-top','0');
};
function storeCategoryContent(categoryContent) {
let activeuser = $('.navbar-login a').first().text().trim(); // expect username here
try {
w.localStorage.setItem('allCategories_' + activeuser, JSON.stringify(categoryContent));
} catch(error) {
console.log(error);
alert(error.toString());
}
}
function restoreCategoryContent() {
if (w.localStorage.getItem('allCategories')) { // convert old single storage to new storage per user
let categoryContentJSON = JSON.parse(w.localStorage.getItem('allCategories')) || [];
storeCategoryContent(categoryContentJSON);
w.localStorage.removeItem('allCategories');
return categoryContentJSON;
}
let activeuser = $('.navbar-login a').first().text().trim(); // expect username here
return JSON.parse(w.localStorage.getItem('allCategories_' + activeuser)) || [];
}
function removeCategoryContent() {
let activeuser = $('.navbar-login a').first().text().trim(); // expect username here
w.localStorage.removeItem('allCategories_' + activeuser);
}
function missionEditSetup(editScope) {
var editStep = editScope.mission.ui.view;
//overwrite submitMission function to inject categorisation
editScope.submitMission = function() {
var b = WireUtil.convertMissionLocalToWire(editScope.mission)
, d = angular.copy(b);
d.submit = true;
editScope.saving = true;
editScope.savingFailed = false;
editScope.saved = false;
w.$http.post(w.Api.SAVE_MISSION, d).success(function(d) {
editScope.saving = false;
editScope.saved = true;
editScope.savedWireMission = b;
//if a category is selected, push the mission to that category
if (parseInt(editScope.selectedCategoryID) >= 0) {
editScope.categoryContent[editScope.selectedCategoryID].missions.push(editScope.savedWireMission.mission_guid);
editScope.categoryContent[editScope.selectedCategoryID].collapse = false;
editScope.selectedCategoryID = -1;
storeCategoryContent(editScope.categoryContent);
}
w.$location.url("/")
}).error(function(b) {
editScope.saving = false;
editScope.savingFailed = true
})
}
editScope.isBreadcrumbDisabled = function(step){
var validGoTo = false;
switch (step) {
case editScope.EditorScreenViews.TYPE:
case editScope.EditorScreenViews.NAME:
validGoTo = editScope.isTypeValid();
break;
case editScope.EditorScreenViews.WAYPOINTS:
validGoTo = editScope.isTypeValid() && !editScope.detailsErrors.hasErrors;
break;
case editScope.EditorScreenViews.PREVIEW:
validGoTo = editScope.isTypeValid() && !editScope.detailsErrors.hasErrors && !editScope.waypointErrors.hasErrors
}
return !validGoTo;
}
//Replace breadcrumb with something a bit clearer
$(".view").empty();
var newBreadcrumb = "
";
var compiledBread = $compile(newBreadcrumb)(editScope);
$(".view").append(compiledBread);
//view specific fixes
var editCode, editTarget, compiledContent;
switch (editStep){
case editScope.EditorScreenViews.TYPE:
editTarget = $(".type-view .bordered-panel");
//Overhauled UI on Mission Type page, including more editorialising on non-linear missions in banners
$(".type-view .bordered-panel").empty().addClass('ready');
editCode = "
"
+ ""
+ ""
+ ""
+ "
"
+ "
Agents visit portals and field trip markers in a set order.
Best suited to missions in a banner series, or one-offs with a pre-determined route.
"
+ "
Agents visit portals and field trip markers in a set order, but the location of every waypoint beyond the first is hidden, meaning players rely on clues in the waypoint text.
Good for more puzzle-based missions, but please ensure you provide adequate clues for agents to find all the waypoints.
"
+ "
Agents visit portals and field trip markers in any order. Excellent for one-off missions where a specific route isn't required, but terrible for missions in banner serieses.
It is strongly advised that if you are making missions for a banner, you set them as Sequential missions - your rating on IngressMosaik will thank you!
";
break;
case editScope.EditorScreenViews.NAME:
editTarget = $(".name-view .bordered-panel");
//Overhauled UI on Mission Name/Image pages
$(".name-view .bordered-panel").empty().addClass('ready');
editCode = "
"
+ ""
+ "
"
+ "
"
+ ""
+ "
"
+ "
"
+ "
";
break;
case editScope.EditorScreenViews.WAYPOINTS:
//Adding drag-and-drop mission reordering
//First, watch whether the user can see the list of waypoints
editScope.$watch('shouldShowWaypointList()', function() {
//if so, wait half a second and apply the JQueryUI sortable parameter to the list thereof
if (editScope.shouldShowWaypointList()){
setTimeout(() => {
//checks start position on start, end position on end, and sends them to the native change position function
$('#waypoints').sortable({
handle: '.number',
axis: 'y',
start: function(event, ui) {
var start_pos = ui.item.index();
ui.item.data('start_pos', start_pos);
},
update: function (event, ui) {
var start_pos = ui.item.data('start_pos');
var end_pos = ui.item.index();
editScope.$apply(function(){
editScope.changeWaypointPosition(start_pos, end_pos);
});
editScope.mission.definition.waypoints.forEach(function(waypoint){
editScope.setSelectedWaypoint(waypoint);
});
editScope.setSelectedWaypoint(editScope.mission.definition.waypoints[end_pos]);
}
});
}, 500);
}
});
break;
case editScope.EditorScreenViews.PREVIEW:
//If there are user generated categories, add a dropdown to add the new mission to one
editScope.categoryContent = restoreCategoryContent();
if (editScope.categoryContent.length > 0 && editScope.mission.mission_guid){
var showCategory = true;
editScope.categoryContent.forEach(function(category){
for (var i = 0; i < category.missions.length; i++){
if (category.missions[i] == editScope.mission.mission_guid){
showCategory = false;
break;
}
}
})
if (showCategory){
editScope.selectedCategoryID = -1;
editTarget = $(".preview-buttons");
if (!document.querySelector('.category-dropdown')) { // draw only once
editCode = "
"
+ "
";
}
}
}
break;
}
if (editCode){
compiledContent = $compile(editCode)(editScope);
editTarget.append(compiledContent);
}
//Runs pageChange() function when changing between Edit states
editScope.setView = function(b) {
editScope.pendingSave && (w.$timeout.cancel(editScope.pendingSave), editScope.pendingSave = null);
editScope.save(b);
setTimeout(() => {
pageChange();
}, 500);
}
}
function missionListSetup(missionScope) {
missionScope.getFullMissionData = function(missions){
var dfd = w.$q.defer();
var missionPromises = [];
angular.forEach(missions, function(mission) {
if (mission.missionListState != "DRAFT" && mission.missionListState != "SUBMITTED"){
var mId = mission.mission_guid;
missionPromises.push($http.post(window.origin + "/api/author/getMissionForProfile", {mission_guid: mId}));
} else {
var mId = mission.draft_mission_id || mission.submitted_mission_id;
missionPromises.push($http.post(window.origin + "/api/author/getMission", {mission_id: mId}));
}
});
w.$q.all(missionPromises).then(function(results) {
var missionData = [];
for (var i = 0; i < results.length; i++){
if (results[i] !== undefined){
var f = WireUtil.convertMissionWireToLocal(results[i].data.mission, results[i].data.pois);
missionData.push(f.definition);
} else {missionData.push(null)}
}
dfd.resolve(missionData);
},
function(data, status) {
console.error('error: ', status, data);
}
)
return dfd.promise;
}
missionScope.categoryContent = [];
missionScope.categorisedMissions = [];
missionScope.uncategorisedMissions = [];
missionScope.uncategorisedSort = 'initial';
missionScope.missions = w.$filter("orderBy")(missionScope.missions, 'definition.name');
missionScope.loadingPreview = false;
missionScope.unsortedCollapse = {collapse: JSON.parse(w.localStorage.getItem('unsortedCollapse') || false)};
//handling for legacy data format
if (!!w.localStorage.getItem('categoryNames')){
let oldegoriesLength = parseInt(w.localStorage.getItem('categoriesLength')) || 0,
categoryNames = w.localStorage.getItem('categoryNames') ? w.localStorage.getItem('categoryNames').split(',') : [],
thisCategory;
//create categories in new format
for (var i= 0; i < oldegoriesLength; i++){
thisCategory = {
id: i,
name: categoryNames[i],
missions: w.localStorage.getItem('categoryContent' + i) ? w.localStorage.getItem('categoryContent' + i).split(',') : [],
collapse: true,
sortCriteria: 'initial'
};
missionScope.categoryContent.push(thisCategory);
w.localStorage.removeItem('categoryContent' + i);
}
storeCategoryContent(missionScope.categoryContent);
//now tidy up the legacy data
w.localStorage.removeItem('categoryNames');
w.localStorage.removeItem('categoriesLength');
}
//get data for all categories
missionScope.categoryContent = restoreCategoryContent();
missionScope.selectedCategoryMissionId = null;
//Add position in initial array
for (var i = 0; i < missionScope.missions.length; i++) {
missionScope.missions[i].position = i;
}
//function to calculate distances between two sets of coordinates taken from geodatasource.com
missionScope.distance = function(lat1, lon1, lat2, lon2, unit) {
if ((lat1 == lat2) && (lon1 == lon2)) {
return 0;
}
else {
var radlat1 = Math.PI * lat1/180;
var radlat2 = Math.PI * lat2/180;
var theta = lon1-lon2;
var radtheta = Math.PI * theta/180;
var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
if (dist > 1) {
dist = 1;
}
dist = Math.acos(dist);
dist = dist * 180/Math.PI;
dist = dist * 60 * 1.1515;
if (unit=="K") { dist = dist * 1.609344 }
if (unit=="N") { dist = dist * 0.8684 }
return dist;
}
}
function addRemovableEventListener(element, event, callback) {
const wrapper = e => {
callback(e, () => element.removeEventListener(event, wrapper) );
}
element.addEventListener(event, wrapper);
}
missionScope.submitMission = function(mission) {
let lastpageclicked = false;
let submitclicked = false;
addRemovableEventListener(document.body, 'DOMNodeInserted', (e, closeListener) => {
let lastpage = document.querySelector('.pagination li:last-child');
if (lastpage && lastpage.innerText == 'Preview') {
if (!lastpage.classList.contains('active')) {
if (!lastpageclicked) {
lastpageclicked = true;
setTimeout(function() { lastpage.querySelector('a').click(); }); // wait until click event is assigned
}
} else {
let nextbutton = document.querySelector('.preview-buttons button:last-child');
if (nextbutton) {
if (!submitclicked) {
submitclicked = true;
setTimeout(function() { nextbutton.click(); });
closeListener();
}
}
}
}
});
missionScope.button1Clicked(mission);
}
missionScope.previewMission = function(guid) {
$('#previewMissionModel .modal-body .notloading').empty();
missionScope.loadingPreview = true;
var mission = w.$filter('filter')(missionScope.missions, {mission_guid: guid})[0];
if (mission){
missionScope.getFullMissionData([mission]).then(function(data){
mission.definition = data[0];
//calculate approx. mission length
var distance = 0;
for (var i = 1; i < mission.definition.waypoints.length; i++){
distance += missionScope.distance(
mission.definition.waypoints[i-1]._poi.location.latitude,
mission.definition.waypoints[i-1]._poi.location.longitude,
mission.definition.waypoints[i]._poi.location.latitude,
mission.definition.waypoints[i]._poi.location.longitude,
"K")
}
missionScope.loadingPreview = false;
w.$injector.invoke(function($compile) {
var modalContent = "";
var compiledContent = $compile(modalContent)(missionScope);
// Put the output of the compilation in to the page using jQuery
$('#previewMissionModel .modal-body .notloading').append(compiledContent);
if (distance < 1){
distance = (Math.floor(distance * 100000) / 100) + "m";
} else {
distance = (Math.floor(distance * 100) / 100) + "km";
}
setTimeout(function(){$('.mission-stats-row').append('
";
//if mission is live, link to mission in Ingress intel and MAT preview thing
if (missionState != "draft" && missionState != "submitted") {
newMissionCode += "
";
return newMissionCode;
}
var generateAllMissions = function() {
// skip if not getting any missions, check if session is logged off!
if (document.querySelector('.welcome-panel')) {
// something is wrong, this should not be here when drawing missions
console.log("WARNING: something is wrong, welcome-panel visible, this should not be here when loading missions");
return;
}
missionScope.toggleCheckboxes(true);
let scrollPosition = w.pageYOffset;
$(".missions-list").empty();
missionScope.categoryContent.length == 0 && ($(".missions-list").addClass("row"));
missionScope.sortCriteria = [];
w.$injector.invoke(function($compile) {
var missionContent = "";
if (missionScope.categoryContent.length > 0) {
//if there are user-defined categories, first sort the categorised/uncategorised missions
missionScope.categorisedMissions = [];
missionScope.uncategorisedMissions = angular.copy(missionScope.missions);
// cleanup removed missions from categoryContent
let existingmissionguids = missionScope.uncategorisedMissions.map((el)=>{return el.mission_guid;});
let removedmissionscount = 0;
missionScope.categoryContent.forEach((category)=>{
// filter out missing guids
let removedmissions = category.missions.filter((guid)=>{return existingmissionguids.indexOf(guid) == -1;});
category.missions = category.missions.filter((guid)=>{return existingmissionguids.indexOf(guid) > -1;});
if (removedmissions.length) {
removedmissionscount += removedmissions.length;
console.log("clean up removed missions",removedmissions);
}
});
if (removedmissionscount > 0) storeCategoryContent(missionScope.categoryContent);
//loop through each category of GUIDs, and add actual missions to scope
missionScope.categoryContent.forEach(function(category) {
var catobj = [];
category.missions.forEach(function(guid) {
//TODO: Do this with filters, not loops
for (var i = 0; i < missionScope.uncategorisedMissions.length ; i++) {
//once the right mission is found, add position, push it to the holding array and remove from uncategorised
if (missionScope.uncategorisedMissions[i].mission_guid == guid) {
catobj.push(missionScope.uncategorisedMissions[i]);
missionScope.uncategorisedMissions.splice(i, 1);
break;
}
}
})
missionScope.categorisedMissions.push(catobj);
})
//then get the categories sorted
missionScope.categoryContent.forEach(function(category) {
category.sortCriteria != 'initial' && (missionScope.categorisedMissions[category.id] = w.$filter("orderBy")(missionScope.categorisedMissions[category.id], category.sortCriteria));
})
missionScope.uncategorisedSort != 'initial' && (missionScope.uncategorisedMissions = w.$filter("orderBy")(missionScope.uncategorisedMissions, missionScope.uncategorisedSort));
//once all the categorisation is done, create the HTML for the categories!
missionContent += "
";
for (var i = 0; i < missionScope.categoryContent.length; i++) {
missionContent += "
";
for (let i = 0; i < missionScope.uncategorisedMissions.length; i++) {
missionContent += generateMission(missionScope.uncategorisedMissions[i], missionScope.uncategorisedMissions[i].position, false);
}
missionContent += "
";
} else {
//if no user-defined categories, just loop through the missions
missionContent += "
"
+ generateSort('all')
+ "
";
for (let i = 0; i < missionScope.missions.length; i++) {
let mission = missionScope.missions[i];
missionContent += generateMission(mission, i, false);
}
}
//modal for moving missions to categories
missionContent += "
{{missionTitle}}
"
+ "
";
//modal for previewing missions
missionContent += "
Preview Mission
"
+ "
"
+ "
";
// Pass our fragment content to $compile, and call the function that $compile returns with the scope.
var compiledContent = $compile(missionContent)(missionScope);
// Put the output of the compilation in to the page using jQuery
$('.missions-list').append(compiledContent);
setTimeout(() => {
w.scrollTo(0, scrollPosition);
}, 250);
//now enable drag-drop for the missions!
if (missionScope.categoryContent.length > 0) {
$('.row[id^=category]').sortable({
items: "div.missionbox",
connectWith: ".row[id^=category]",
tolerance: "pointer",
placeholder: "ui-state-highlight col-xs-12 col-sm-6 col-md-3",
start: function(event, ui) {
var start_pos = ui.item.index();
ui.item.data('start_pos', start_pos);
ui.item.data('start_cat', ui.item[0].parentElement.id);
},
update: function (event, ui) {
var start_pos = ui.item.data('start_pos'),
start_cat = ui.item.data('start_cat'),
end_pos = ui.item.index(),
end_cat = ui.item[0].parentElement.id;
!ui.sender && missionScope.dragMissions(start_pos, start_cat, end_pos, end_cat);
}
});
}
});
}
missionScope.exportData = function() {
navigator.clipboard.writeText(JSON.stringify(missionScope.categoryContent)).then(function() {
alert('Category data copied to your clipboard - please paste this in the Import Data box in the browser you want to export this to')
}, function() {
alert('Something went wrong')
});
};
missionScope.exportDataFile = function() {
let jsondata = JSON.stringify(missionScope.categoryContent);
let filename = 'imattc-category-data.json';
missionScope.downloadJSONFile(jsondata,filename);
};
function parseData(data) {
if (data == null || data == "") return; // don't do anything
//try to parse data, then turn it into object
try {
let parseddata = JSON.parse(data);
if (!(parseddata instanceof Array)) return; // json must be an [] array
let validdata = [];
for (let cat of parseddata) {
if (cat instanceof Object && !(cat instanceof Array)) {
let validcat = {id:-1,name:'',missions:[],collapse:false,sortCriteria:'initial'};
for (let el in cat) {
if (typeof cat[el] == typeof validcat[el]) {
validcat[el] = cat[el];
}
}
if (validcat.id >= 0) validdata.push(validcat);
}
}
missionScope.categoryContent = validdata;
storeCategoryContent(missionScope.categoryContent);
generateAllMissions();
} catch (e){
alert('Importing data failed!!');
console.log('Parsing JSON data failed',data);
}
}
missionScope.importData = function() {
let dataInput = prompt("Please paste in the text you got from the Data Export on your other device");
parseData(dataInput);
}
missionScope.importDataFile = function() {
function loadjson(event) {
let data = event.target.result;
parseData(data);
}
function loadfile(element) {
if (!element.files.length) return;
let reader = new FileReader();
reader.onload = loadjson;
reader.readAsText(element.files[0]);
}
let fileinput = document.createElement('input');
fileinput.type = 'file';
fileinput.accept = 'application/json,text/plain';
fileinput.addEventListener('change',function(e) {
e.preventDefault();
loadfile(this);
},false);
fileinput.click();
};
if (document.querySelector('.welcome-panel')) {
// something is wrong, this should not be here when drawing missions
if (document.querySelector('.design-message')) document.querySelector('.design-message').innerText = "You need to reload the page and Sign in again.";
if (document.querySelector('.create-new')) document.querySelector('.create-new').remove();
addpoweredbymessage(document.querySelector('.welcome-panel'));
} else {
//compiling the buttons
w.$injector.invoke(function($compile) {
var buttonContent = "