dev_group_list2

Better Submit-to-group dialog

目前为 2024-01-17 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name dev_group_list2
  3. // @namespace http://www.deviantart.com/
  4. // @version 5.9
  5. // @description Better Submit-to-group dialog
  6. // @author Dediggefedde
  7. // @match https://www.deviantart.com/*
  8. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js
  9. // @grant GM.xmlHttpRequest
  10. // @grant GM.setValue
  11. // @grant GM.getValue
  12.  
  13. // ==/UserScript==
  14. /* globals $*/
  15. // @require http://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
  16.  
  17. //dgl2=dgl2 identifiert/classname
  18. (function() {
  19. 'use strict';
  20. let userName = "";
  21. let userId=0;
  22. let moduleID = 0;
  23. let grPerReq = 24;
  24. let groups = []; //{userId,useridUuid,username,usericon,type,isNewDeviant,latestDate}
  25. const inactiveDate = new Date();
  26. inactiveDate.setMonth(inactiveDate.getMonth() - 3); //inactive if latest submission before 3 months
  27. // isNewDeviant not needed.
  28. // type=oneof{group, super group}
  29. // usericon always starts with https://a.deviantart.net/avatars-big
  30. // useridUuid specific to the group, contains submission rights
  31. // userid of the group
  32. // latestDate newest publish date of thumb for a folder; filled later when folders requested
  33. let groupN = 0;
  34. let listedGroups = [];
  35. let devID;
  36. let collections = [{
  37. id: 0,
  38. name: "all",
  39. groups: [],
  40. showing: 1
  41. }];
  42. let collectionOrder = [];
  43. let colMode = 0; //0 show, 1 add, 2 remove, 3 delete
  44. let colModeTarget = 0;
  45. let macros = []; //{id, name, data:[{folderName, folderID, groupID, type}]}
  46. let macroOrder = [];
  47. let macroMode = 0; //0 idle, 1 record, 2 play, 3 remove
  48. let macroModeTarget = 0; //for recording
  49. let lastFilter = 0;
  50. let colListMode = 0; //0 collection, 1 macro;
  51. let svgTurnArrow = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1.507856 1.2692268" style="height:0.5em;"> <defs id="defs2"> <marker id="Arrow2Send" style="overflow:visible"> <path id="path8454" style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" transform="matrix(-0.3,0,0,-0.3,0.69,0)" /> </marker> </defs> <g id="layer1" transform="translate(-1.2565625,-0.79875001)"> <path style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Send)" d="m 2.38125,0.93125 c -1.3229167,0 -1.3229166,0.79375 0,0.79375" id="path8419"/> </g> </svg> ';
  52. let fetchingGroups = false; //prevent refresh button requesting multiple times at once
  53. let entityMap = {
  54. '&': '&amp;',
  55. '<': '&lt;',
  56. '>': '&gt;',
  57. '"': '&quot;',
  58. "'": '&#39;',
  59. '/': '&#x2F;',
  60. '`': '&#x60;',
  61. '=': '&#x3D;'
  62. };
  63. let loadedFolders = new Map();
  64. let lastGroupClickID;
  65. let notScanThisInstance = false;
  66. let targetName = ""; //target group or macro name
  67.  
  68. const errtyps = Object.freeze({
  69. Connection_Error: "Connection Error",
  70. No_User_ID: "No User ID",
  71. Unknown_Error: "Site Error",
  72. Parse_Error: "Parse Error",
  73. Wrong_Setting: "Wrong Profile setting"
  74. });
  75. //error protocol convention: ErrType of errtyps, ErrDescr as text, ErrDetail with exception object
  76. //alert of type/descr, console log with detail
  77.  
  78. function err(type, descr, detail) {
  79. return { ErrType: type, ErrDescr: descr, ErrDetail: detail };
  80. }
  81.  
  82. function errorHndl(err) {
  83. myMsgBox("<strong>Type</strong>: " + err.ErrType + "<br /><strong>Description</strong>: " + err.ErrDescr + "<br /><br />See the log (F12 in Chrome/Firefox) for more details.<br />If the error persist, feel free to write me a <a style=\"text-decoration: underline;\" href=\"https://www.deviantart.com/dediggefedde/art/dev-group-list2-817465905#comments\">comment</a>.", "Error");
  84. console.log("dev_group_list2 error:", err);
  85. }
  86.  
  87. //API calls getting data
  88. function fillGroups(offset) { //async+callback //load all groups
  89.  
  90. let token = $("input[name=validate_token]").val();
  91. let murl = "https://www.deviantart.com/_puppy/gruser/module/groups/members?username=" + userName + "&moduleid=" + moduleID +"&gruserid="+userId+"&gruser_typeid=4"+ "&offset=" + offset + "&limit=" + grPerReq + "&csrf_token=" + token;
  92. return new Promise(function(resolve, reject) {
  93. GM.xmlHttpRequest({
  94. method: "GET",
  95. url: murl,
  96. onerror: function(response) {
  97. reject(err(errtyps.Connection_Error, "Connection to " + murl + " failed", response));
  98. },
  99. onload: function(response) {
  100. if (response.status == 500) {
  101. reject(err(errtyps.Connection_Error, "Connection error " + murl + " failed", response));
  102. return
  103. }
  104. let resp = JSON.parse(response.responseText);
  105.  
  106. if (offset == 0) groups = [];
  107.  
  108. let newnams = resp.results.map(x => x.userId);
  109. if (groups.map(x => x.userId).filter(x => newnams.includes(x)).length > 0) {
  110. reject(err(errtyps.Wrong_Setting, "Double group entries detected. Probably wrong sorting at profile/about's group-member section. Please choose asc or desc, not random.", groups));
  111. $("#dgl2_refresh").css("cursor", "pointer");
  112. return;
  113. }
  114.  
  115. groups = groups.concat(resp.results);
  116. groupN = resp.total;
  117.  
  118. let frac = 0;
  119. if (groupN > 0) frac = groups.length / groupN * 100;
  120. frac = Math.round(frac);
  121.  
  122. $("#dgl2_refresh rect").css("fill", "url(#dgl2_grad1)");
  123. $("#dgl2_grad1_stop1").attr("offset", frac + "%");
  124. $("#dgl2_grad1_stop2").attr("offset", (frac + 1) + "%");
  125. $("#dgl2_refresh").attr("title", frac + " %");
  126. $("span.dgl2_descr").text("Loading List of Groups... " + frac + " %");
  127.  
  128. if (resp.hasMore) {
  129. resolve(fillGroups(resp.nextOffset));
  130. } else {
  131. GM.setValue("groups", JSON.stringify(groups));
  132. insertGroups();
  133. $("#dgl2_refresh rect").css("fill", "");
  134. $("#dgl2_refresh").css("cursor", "pointer");
  135.  
  136. resolve(groups);
  137. }
  138. }
  139. });
  140. });
  141. }
  142.  
  143. function grabIDfromPage(name) {
  144. return new Promise(function(resolve, reject) {
  145. GM.xmlHttpRequest({
  146. method: "GET",
  147. url: "https://www.deviantart.com/" + name,
  148. onerror: function(response) {
  149. reject(err(errtyps.Connection_Error, "Connection to https://www.deviantart.com/" + name + " failed", response));
  150. },
  151. onload: async function(response) {
  152.  
  153. $("#dgl2_refresh rect").css("fill", "url(#dgl2_grad1)");
  154. ngrpCnt += 1;
  155. $("span.dgl2_descr").text(`Loading List of Groups IDs... ${ngrpCnt}/${ngrpleft}`);
  156.  
  157. let rex = /itemid":(.*?),"friendid":"(.*?)"/i;
  158. let mat = response.responseText.match(rex);
  159. if (mat == null) {
  160. reject(err(errtyps.No_User_ID, "Request of " + name + "-id failed", response));
  161. return;
  162. }
  163.  
  164. groups.forEach(gr => {
  165. if (gr.username == name) {
  166. gr.userId = mat[1];
  167. gr.useridUuid = mat[2];
  168. }
  169. });
  170. GM.setValue("groups", JSON.stringify(groups));
  171. resolve(mat[1]);
  172. }
  173. });
  174. });
  175. }
  176.  
  177. function fillSubFolder(groupID, type, name) { //async+callback //type =[collection,gallery]
  178.  
  179. if (groupID == "undefined") {
  180. $(".dgl2_groupdialog ").css("cursor", "wait");
  181. $(".dgl2_groupButton").css("cursor", "wait");
  182. return grabIDfromPage(name).then(id => {
  183. groupID = id;
  184. lastGroupClickID = id;
  185. return fillSubFolder(groupID, type, name);
  186. }).catch(erg => {
  187. errorHndl(erg);
  188. $(".dgl2_groupdialog ").css("cursor", "pointer");
  189. return null;
  190. });
  191. }
  192.  
  193. return new Promise(function(resolve, reject) {
  194. let token = $("input[name=validate_token]").val();
  195. let murl=`https://www.deviantart.com/_puppy/dadeviation/group_folders?groupid=${groupID}&type=${type}&csrf_token=${token}`;
  196. GM.xmlHttpRequest({
  197. method: "GET",
  198. url: murl,
  199. onerror: function(response) {
  200. reject(err(errtyps.Connection_Error, "Connection to " + murl + " failed", response));
  201. },
  202. onload: async function(response) {
  203. let resp;
  204. let errg;
  205. try {
  206. resp = JSON.parse(response.responseText);
  207. } catch (ex) {
  208. errg = err(errtyps.Connection_Error, `Page problems reaching ${murl} Please try again later!` , ex)
  209. reject(errg)
  210. }
  211. if (typeof resp.results == "undefined") {
  212. errg = err(errtyps.Parse_Error, "Error parsing website response. Private browser mode active?", response);
  213. errorHndl(errg);
  214. } else {
  215. let latestDate = Math.max(...Object.values(resp.results).map(o => o.thumb ? new Date(o.thumb.publishedTime) : null));
  216. let grInd = groups.findIndex(item => item.userId == groupID);
  217. groups[grInd].latestDate = latestDate;
  218. let but = document.querySelector(`button.dgl2_groupButton[groupid='${groupID}']`);
  219. if (latestDate != null && but != null) {
  220. but.setAttribute("title", escapeHtml(groups[grInd].username) + "\n Last submission: " + (new Date(latestDate)).toLocaleString());
  221. but.setAttribute("activity", (inactiveDate < new Date(latestDate)) ? "active" : "inactive");
  222. }
  223. insertSubFolders(resp.results);
  224. if (macroMode == 1) {
  225. for (let gr of macros[macroModeTarget].data) {
  226. if (gr.groupID == groupID) {
  227. $("button.dgl2_groupButton[folderID='" + gr.folderID + "']").addClass("folderInMacro");
  228. break;
  229. }
  230. }
  231. }
  232. }
  233. $(".dgl2_groupdialog").css("cursor", "");
  234. $(".dgl2_groupButton").css("cursor", "pointer");
  235. $("div.groupPopup").focus();
  236. resolve(resp.results);
  237.  
  238. }
  239. });
  240. });
  241. }
  242.  
  243. function fillModuleID() { //async+callback //get Module ID for group submission
  244. let murl = "https://www.deviantart.com/" + userName + "/about";
  245. return new Promise(function(resolve, reject) {
  246. GM.xmlHttpRequest({
  247. method: "GET",
  248. url: murl,
  249. onerror: function(response) {
  250. reject(err(errtyps.Connection_Error, "Connection to " + murl + " failed", response));
  251. },
  252. onload: async function(response) {
  253. try {
  254. let resp = (response.responseText);
  255. resp = resp.match(/{\\\"id\\\":(\d+),\\\"type\\\":\\\"group_list_members/i);
  256. if (resp != null && resp.length > 1) {
  257. moduleID = resp[1];
  258. resolve(moduleID);
  259. } else {
  260. reject(err(errtyps.Wrong_Setting, "No group-member section in /about page", resp));
  261. }
  262. } catch (ex) {
  263. reject(err(errtyps.Unknown_Error, "Something went wrong while accessing groups", ex));
  264. }
  265. }
  266. });
  267. });
  268. }
  269.  
  270. function fillListedGroups(devID,cursor) {
  271. let token = $("input[name=validate_token]").val();
  272. let murl = `https://www.deviantart.com/_puppy/dadeviation/featured_in_groups?deviationid=${devID}&limit=25&csrf_token=${token}&cursor=${cursor}`;
  273. return new Promise(function(resolve, reject) {
  274. GM.xmlHttpRequest({
  275. method: "GET",
  276. url: murl,
  277. onerror: function(response) {
  278. reject(err(errtyps.Connection_Error, "Connection to " + murl + " failed", response));
  279. },
  280. onload: async function(response) {
  281. let res;
  282. try{
  283. res = JSON.parse(response.responseText);
  284. }catch(ers){
  285. reject(err(errtyps.Connection_Error, "Reading " + murl + " failed", {ers,response}));
  286. return;
  287. }
  288. for (let entr of res.results){
  289. listedGroups.push(parseInt(entr.userId));
  290. }
  291.  
  292. if(res.cursor)
  293. resolve(fillListedGroups(devID,res.cursor));
  294. else
  295. resolve(listedGroups);
  296. }
  297. });
  298. });
  299.  
  300. }
  301.  
  302. function requestAddSubmission(devID, folderID, groupID, type) { //async+callback //type =[collection,gallery]
  303. let macroFchanged = false;
  304. if (macroMode == 1) {
  305. if (macros[macroModeTarget].data.some(e => e.groupID === groupID)) {
  306. macros[macroModeTarget].data.forEach(function(el) {
  307. if (el.groupID === groupID) {
  308. el.folderID = folderID;
  309. el.folderName = loadedFolders.get(folderID);
  310. }
  311. }); //change folder of present group
  312. macroFchanged = true;
  313. } else { //don't add if included already
  314. macros[macroModeTarget].data.push({
  315. folderName: loadedFolders.get(folderID),
  316. folderID: folderID,
  317. groupID: groupID,
  318. type: type
  319. });
  320. }
  321. GM.setValue("macros", JSON.stringify(macros));
  322. }
  323.  
  324. return new Promise(function(resolve, reject) {
  325. let token = $("input[name=validate_token]").val();
  326. let dat = {
  327. "groupid": parseInt(groupID),
  328. "type": type.toString(),
  329. "folderid": parseInt(folderID),
  330. "deviationid": parseInt(devID),
  331. "csrf_token":token.toString(),
  332. };
  333. let murl= `https://www.deviantart.com/_puppy/dadeviation/group_add`;
  334. if (macroMode == 1) { //don't submit while adding to macros
  335. resolve({ success: true, gname: groupNameById(groupID), fname: loadedFolders.get(folderID), fchanged: macroFchanged });
  336. } else {
  337. GM.xmlHttpRequest({
  338. method: "POST",
  339. url: murl,
  340. headers: {
  341. "Accept": 'application/json, text/plain, */*',
  342. "Accept-Language":"de,en-US;q=0.7,en;q=0.3",
  343. "Content-Type": 'application/json',
  344. "Pragma":"no-cache",
  345. "Cache-Control":"no-cache"
  346. },
  347. dataType: 'json',
  348. data: JSON.stringify(dat),
  349. onerror: function(response) {
  350. response.gname = groupNameById(groupID);
  351. reject(err(errtyps.Connection_Error, "Connection to https://www.deviantart.com/_puppy/dadeviation/group_add failed", response));
  352. },
  353. onload: async function(response) {
  354. let resp = JSON.parse(response.responseText);
  355. resp.gname = groupNameById(groupID);
  356. resolve(resp);
  357. }
  358. });
  359. }
  360. });
  361. }
  362.  
  363. function playMacro(index) {
  364. macroMode = 2;
  365. let promises = [];
  366. for (let d of macros[index].data) {
  367. promises.push(requestAddSubmission( devID, d.folderID, d.groupID, d.type));
  368. }
  369. Promise.all(promises).catch(err => {
  370. alert(macros[index].name + " Error!<br/>" + JSON.stringify(macros[index].data) + " " + JSON.stringify(err), "Error");
  371. }).then(res => {
  372. myMsgBox(
  373. res.map(obj => {
  374. let retval = "<strong>" + obj.gname + "</strong>: "
  375. if (obj.success) {
  376. retval += "Success! ";
  377. if (obj.needsVote == true) retval += " Vote pending";
  378. } else {
  379. retval += "Error! ";
  380. if (obj.errorDetails) retval += obj.errorDetails;
  381. }
  382. return retval;
  383. }).join("<br/>"), "Play Macro " + macros[macroModeTarget].name);
  384. })
  385. macroMode = 0;
  386. }
  387. //event handlers
  388. function Ev_groupClick(event) { //event propagation
  389. event.stopPropagation();
  390.  
  391. let targetBut = $(event.target).closest(".dgl2_groupButton");
  392. let groupID = targetBut.attr("groupID");
  393. let groupNam = targetBut.attr("groupname");
  394.  
  395. if (groupID == "undefined") {
  396. grabIDfromPage(groupNam).then(id => {
  397. $(event.target).closest(".dgl2_groupButton").attr("groupID", id);
  398. Ev_groupClick(event);
  399. });
  400. return;
  401. }
  402. let elInd;
  403. switch (colMode) {
  404. case 1: //add
  405. elInd = collections[colModeTarget].groups.indexOf(groupID);
  406. if (elInd == -1) {
  407. collections[colModeTarget].groups.push(groupID);
  408. GM.setValue("collections", JSON.stringify(collections));
  409. targetBut.addClass("dgl2_inCollection");
  410. targetName = targetBut.attr("groupName");
  411. }
  412. break;
  413. case 2: //remove
  414. targetName = collections[colModeTarget].name;
  415. switch (colListMode) {
  416. case 0: //collection
  417. elInd = collections[colModeTarget].groups.indexOf(groupID);
  418. if (elInd > -1) {
  419. collections[colModeTarget].groups.splice(elInd, 1);
  420. GM.setValue("collections", JSON.stringify(collections));
  421. }
  422. insertFilteredGroups(colModeTarget);
  423. break;
  424. case 1: //macro
  425. for (elInd = 0; elInd < macros[macroModeTarget].data.length; ++elInd) {
  426. if (macros[macroModeTarget].data[elInd].groupID == groupID) break;
  427. }
  428.  
  429. if (elInd < macros[macroModeTarget].data.length) {
  430. macros[macroModeTarget].data.splice(elInd, 1);
  431. GM.setValue("macros", JSON.stringify(macros));
  432. }
  433. insertMacroGroups(macroModeTarget);
  434. break;
  435. }
  436. break;
  437. case 0:
  438. default:
  439. if (targetBut.attr("type") == "group") {
  440. lastGroupClickID = targetBut.attr("groupID");
  441. fillSubFolder(groupID, "gallery", groupNam);
  442. if (macroMode != 1) {
  443. targetName = targetBut.attr("groupName");
  444. } else {
  445.  
  446. }
  447. displayModeText();
  448. //Add this deviation to a group folder
  449. } else if (targetBut.attr("type") == "folder") {
  450. requestAddSubmission(devID, targetBut.attr("folderID"), targetBut.attr("groupID"), targetBut.attr("folderType")).then(function(arg) {
  451. if (arg.success == true) {
  452. if (macroMode == 1) {
  453. if (arg.fchanged) {
  454. myMsgBox(arg.gname + " target folder changed to " + arg.fname, "Info");
  455. } else {
  456. myMsgBox(arg.gname + "/" + arg.fname + " added to macro", "Info");
  457. }
  458. insertMacros(); //update titles
  459. insertGroups(); //go back to groups view
  460.  
  461. $("div.dgl2_groupWrapper").addClass("dgl2_addGroup");
  462. displayModeText();
  463. //$("span.dgl2_descr").text("macro " + macros[macroModeTarget].name + " is recording.");
  464. for (let el of macros[macroModeTarget].data) {
  465. $("button.dgl2_groupButton[groupID=" + el.groupID + "]").addClass("dgl2_inCollection");
  466. }
  467. macroMode = 1;
  468. let lastgrBut = $("button[groupid=" + lastGroupClickID + "]")
  469. lastgrBut[0].scrollIntoView();
  470. lastgrBut.addClass("shadow-pulse");
  471. } else {
  472. let retfun = function() {
  473. $("span.dgl2_titleText").click();
  474. let lastgrBut = $("button[groupid=" + lastGroupClickID + "]")
  475. lastgrBut[0].scrollIntoView();
  476. lastgrBut.addClass("shadow-pulse");
  477. };
  478. if (arg.needsVote) {
  479. myMsgBox("Success! Submission pending group's vote", "Info").then(retfun);
  480. } else {
  481. myMsgBox("Success! Submission added to group", "Info").then(retfun);
  482. }
  483. }
  484.  
  485. } else {
  486. throw arg;
  487. }
  488. /*
  489. deviationGroupCount: 1
  490. needsVote: true
  491. */
  492. }).catch(function(arg) {
  493. let tx = "deviation-ID: " + devID + "<br/>" +
  494. "Group-Name: " + (arg.gname ? arg.gname : "Unknown") + "<br/>" +
  495. (arg.errorDescription ? arg.errorDescription : "Unexpected error.") + "<br/>" +
  496. (arg.errorDetails ? JSON.stringify(arg.errorDetails) : JSON.stringify(arg))
  497. let errg = err(errtyps.Unknown_Error, tx, arg);
  498. errorHndl(errg);
  499. });
  500. }
  501. }
  502. displayModeText();
  503. }
  504.  
  505. function Ev_ContextSubmit(event) {
  506. event.stopPropagation();
  507. event.preventDefault();
  508. event.target = $("#dgl2_grContext select option:selected").get(0);
  509. Ev_groupClick(event);
  510. $("#dgl2_grContext").hide().find("select").empty();
  511. }
  512.  
  513. function Ev_groupContext(event) {
  514. event.stopPropagation();
  515. event.preventDefault();
  516. let el = $("#dgl2_grContext");
  517. if (el.length == 0) {
  518. el = $("<div id='dgl2_grContext'><span class='desc'>Submit to a Folder</span><br /><select size=5></select><br/><button>Submit</button></div>").appendTo(document.body);
  519. el.find("button").click(Ev_ContextSubmit);
  520. }
  521. el.find("select").hide();
  522. el.finish().show().css({
  523. top: event.pageY + "px",
  524. left: event.pageX + "px"
  525. });
  526. let groupID = $(event.target).closest("button.dgl2_groupButton").attr("groupid")
  527. fillSubFolder(groupID, "gallery", $(event.target).closest("button.dgl2_groupButton").attr("groupname")).then(function() {
  528. el.find("select").show().focus().get(0).selectedIndex = 0;
  529. });
  530.  
  531. }
  532.  
  533. function switchColList() {
  534. switch (colListMode) {
  535. case 0:
  536. colListMode = 1;
  537. $("span.dgl2_CollTitle").html("Macros");
  538. insertMacros();
  539. break;
  540. case 1:
  541. $("span.dgl2_CollTitle").html("Collections");
  542. colListMode = 0;
  543. insertCollections();
  544. }
  545. }
  546.  
  547. function highlightLetter(which) {
  548.  
  549. $(".dgl2_letterfound").removeClass("dgl2_letterfound");
  550. $(".dgl2_groupdialog button.dgl2_groupButton[groupName^='" + which + "' i]").addClass("dgl2_letterfound").focus();
  551. $(".dgl2_groupdialog button.dgl2_groupButton[folderName^='" + which + "' i]").addClass("dgl2_letterfound").focus();
  552. }
  553.  
  554. function Ev_colListClick(event) {
  555. event.stopPropagation();
  556.  
  557. if ($(event.target).closest(".dgl2_CollTitleBut").length > 0) {
  558. switchColList();
  559. }
  560. let id = $(event.target).closest("li").first().attr("colID");
  561. if (typeof id == "undefined" && $(event.target).closest("button").hasClass("dgl2_topBut")) id = 0;
  562. else if (typeof id == "undefined") return;
  563. let clasNam = $(event.target).closest("button").attr("class");
  564. let index;
  565. if (colListMode == 0) index = colIndexById(id);
  566. else if (colListMode == 1) index = makIndexById(id);
  567. $("div.dgl2_groupWrapper").removeClass("dgl2_addGroup").removeClass("dgl2_remGroup");
  568. $("button.dgl2_inCollection").removeClass("dgl2_inCollection");
  569. let el;
  570. let obj, dat;
  571. let d = new Date();
  572. let con;
  573. let nam;
  574. targetName = "";
  575.  
  576. if (clasNam) clasNam = clasNam.replace(" dgl2_topBut", "");
  577. switch (clasNam) {
  578. case "dgl2_export":
  579. obj = {
  580. collections: collections,
  581. collectionOrder: collectionOrder,
  582. macros: macros,
  583. macroOrder: macroOrder
  584. };
  585. dat = d.getFullYear() + ("0" + d.getMonth()).slice(-2) + ("0" + d.getDate()).slice(-2) + "-" + ("0" + d.getHours()).slice(-2) + ("0" + d.getMinutes()).slice(-2) + ("0" + d.getSeconds()).slice(-2);
  586. download(JSON.stringify(obj), "dev_group_list2_data_" + dat + ".txt");
  587. break;
  588. case "dgl2_import":
  589. upload().then(function(imp) {
  590. try {
  591. let i;
  592. let obj = JSON.parse(imp);
  593. if (obj.macros && obj.macroOrder) {
  594. macros = obj.macros;
  595. macroOrder = obj.macroOrder;
  596. }
  597. if (obj.collections && obj.collectionOrder) {
  598. collections = obj.collections;
  599. collectionOrder = obj.collectionOrder;
  600. } else if (typeof obj[0] != "undefined" && (obj[0][0].indexOf("_collist") != -1 || obj[1][0].indexOf("_collist") != -1)) { //v1 compatibility mode
  601. collections = [{
  602. id: 0,
  603. name: "all",
  604. groups: [],
  605. showing: 1
  606. }];
  607. let ind = (obj[0][0].indexOf("_collist") != -1) ? 0 : ((obj[1][0].indexOf("_collist") != -1) ? 1 : -1)
  608. let oldList = obj[ind][1].split("\u0002");
  609. let coll = oldList.map((list, ind) => {
  610. let entries = list.split("\u0001");
  611. let nam = entries.shift();
  612. return {
  613. id: ind + 1,
  614. name: nam,
  615. groups: entries.map(el => {
  616. return $("button[groupname='" + el + "']").attr("groupid");
  617. }).filter(el => typeof el != "undefined"),
  618. showing: 1
  619. }
  620. });
  621. collections = collections.concat(coll).sort((a, b) => a.id > b.id);
  622. collectionOrder = collections.map(col => "dgl2item-" + col.id);
  623. } else {
  624. throw "No collections found!";
  625. }
  626. //clean up old groups not beeing a member of anymore
  627. for (i in macros) {
  628. macros[i].data = macros[i].data.filter(el => { return groupNameById(el.groupID) != "" });
  629. }
  630. for (i in collections) {
  631. collections[i].groups = collections[i].groups.filter(el => { return groupNameById(el) != "" });
  632. }
  633. } catch (ex) {
  634. errorHndl(err(errtyps.Parse_Error, "Not a valid dev_group_list2 file", ex));
  635. return;
  636. }
  637.  
  638. GM.setValue("collectionOrder", JSON.stringify(collectionOrder));
  639. GM.setValue("collections", JSON.stringify(collections));
  640. GM.setValue("macroOrder", JSON.stringify(macroOrder));
  641. GM.setValue("macros", JSON.stringify(macros));
  642. myMsgBox("Import successfull!", "Info");
  643. insertGroups();
  644. insertCollections();
  645. if (colListMode != 0) switchColList();
  646. });
  647. break;
  648. case "dgl2_add":
  649. insertGroups();
  650. switch (colListMode) {
  651. case 0: //collection
  652.  
  653. targetName = collections[index].name;
  654. //$("span.dgl2_descr").text("Add groups to the collection " + collections[index].name);
  655. colMode = 1;
  656. colModeTarget = index;
  657. $("div.dgl2_groupWrapper").addClass("dgl2_addGroup");
  658. for (el of collections[index].groups) {
  659. $("button.dgl2_groupButton[groupID=" + el + "]").addClass("dgl2_inCollection");
  660. }
  661. displayModeText();
  662. break;
  663. case 1:
  664. colMode = 0;
  665. insertGroups();
  666. $("div.dgl2_groupWrapper").addClass("dgl2_addGroup");
  667. targetName = macros[index].name;
  668. //$("span.dgl2_descr").text("macro " + macros[index].name + " is recording.");
  669. for (el of macros[index].data) {
  670. $("button.dgl2_groupButton[groupID=" + el.groupID + "]").addClass("dgl2_inCollection");
  671. }
  672. macroMode = 1;
  673. macroModeTarget = index;
  674. displayModeText();
  675. break;
  676. }
  677. break;
  678. case "dgl2_sub":
  679. switch (colListMode) {
  680. case 0: //collection
  681. insertFilteredGroups(index);
  682. targetName = collections[index].name;
  683. //$("span.dgl2_descr").text("Remove groups from the collection " + collections[index].name);
  684. colMode = 2;
  685. colModeTarget = index;
  686. $("div.dgl2_groupWrapper").addClass("dgl2_remGroup");
  687. displayModeText();
  688. break;
  689. case 1: //macro
  690. insertMacroGroups(index);
  691. targetName = macros[index].name;
  692. // $("span.dgl2_descr").text("Remove groups from the macro " + macros[index].name);
  693. colMode = 2;
  694. macroMode = 3;
  695. macroModeTarget = index;
  696. $("div.dgl2_groupWrapper").addClass("dgl2_remGroup");
  697. displayModeText();
  698. break;
  699. }
  700. break;
  701. case "dgl2_del":
  702. switch (colListMode) {
  703. case 0: //collection
  704. myMsgBox("Delete Collection " + collections[index].name + " ?", "Collection", 1).then(con => {
  705. if (!con) return;
  706. collections.splice(index, 1);
  707. collectionOrder.splice(collectionOrder.indexOf("dgl2item-" + id), 1);
  708.  
  709. GM.setValue("collectionOrder", JSON.stringify(collectionOrder));
  710. GM.setValue("collections", JSON.stringify(collections));
  711.  
  712. insertGroups();
  713. insertCollections();
  714. });
  715. break;
  716. case 1: //macro
  717. myMsgBox("Delete Macro " + macros[index].name + " ?", "Macro", 1).then(con => {
  718. if (!con) return;
  719. macros.splice(index, 1);
  720. macroOrder.splice(macroOrder.indexOf("dgl2item-" + id), 1);
  721.  
  722. GM.setValue("macroOrder", JSON.stringify(macroOrder));
  723. GM.setValue("macros", JSON.stringify(macros));
  724.  
  725. insertGroups();
  726. insertMacros();
  727. });
  728. break;
  729. }
  730. break;
  731. case "dgl2_new":
  732. switch (colListMode) {
  733. case 0: //collection
  734. el = {
  735. name: "New Collection",
  736. groups: [],
  737. showing: 1
  738. };
  739. el.id = getLowestFree(collections);
  740. collections.push(el);
  741. collectionOrder.push("dgl2item-" + el.id);
  742.  
  743. GM.setValue("collectionOrder", JSON.stringify(collectionOrder));
  744. GM.setValue("collections", JSON.stringify(collections));
  745.  
  746. insertGroups();
  747. insertCollections();
  748. break;
  749. case 1: //macros
  750. el = {
  751. name: "New Macro",
  752. data: []
  753. };
  754. el.id = getLowestFree(macros);
  755. macros.push(el);
  756. macroOrder.push("dgl2item-" + el.id);
  757.  
  758. GM.setValue("macroOrder", JSON.stringify(macroOrder));
  759. GM.setValue("macros", JSON.stringify(macros));
  760.  
  761. insertGroups();
  762. insertMacros();
  763. break;
  764. }
  765. break;
  766. case "dgl2_visible":
  767.  
  768. switch (colListMode) {
  769. case 0: //collection
  770. if (!collections[index].hasOwnProperty("showing")) collections[index].showing = 0;
  771. else collections[index].showing = 1 - collections[index].showing; //toggle 0 and 1
  772. GM.setValue("collections", JSON.stringify(collections));
  773.  
  774. $(event.target).closest("li").attr("active", collections[index].showing);
  775. insertGroups();
  776. break;
  777. case 1: //macro
  778. //donothing
  779. break;
  780. }
  781. break;
  782. case "dgl2_edit":
  783. switch (colListMode) {
  784. case 0: //collection
  785. myMsgBox("Please enter a new collection name!", "Change Collection Name", 2, collections[index].name).then(nam => {
  786. if (!nam) return;
  787. collections[index].name = nam;
  788. GM.setValue("collections", JSON.stringify(collections));
  789. insertCollections();
  790. });
  791. break;
  792. case 1: //macro
  793. myMsgBox("Please enter a new macro name!", "Change Macro Name", 2, macros[index].name).then(nam => {
  794. if (!nam) return;
  795. macros[index].name = nam;
  796. GM.setValue("macros", JSON.stringify(macros));
  797. insertMacros();
  798. });
  799. break;
  800. }
  801. break;
  802. case undefined:
  803. default:
  804. switch (colListMode) {
  805. case 0: //collection
  806. colMode = 0;
  807. insertFilteredGroups(index);
  808. break;
  809.  
  810. case 1: //macro
  811. myMsgBox("Do you want to add this to the following groups?<br/>" + macros[index].data.map(obj => {
  812. return groupNameById(obj.groupID);
  813. }).join(", "), "Submit to Groups", 1).then(con => {
  814. if (!con) {} else {
  815. macroMode = 2;
  816. playMacro(index);
  817. }
  818. displayModeText();
  819. });
  820. break;
  821. }
  822.  
  823. }
  824. displayModeText();
  825. }
  826.  
  827. function Ev_getGroupClick() {
  828.  
  829. if (fetchingGroups) return;
  830. fetchingGroups = true;
  831. groups.forEach((el) => {
  832. if (el.userId == 0) {
  833. el.userId = "undefined"
  834. el.useridUuid = "undefined"
  835. }
  836. });
  837.  
  838. $("span.dgl2_descr").text("Loading Module ID...");
  839. $("#dgl2_refresh").css("cursor", "pointer");
  840. fillModuleID().then(function() {
  841. $("span.dgl2_descr").text("Loading List of Groups...");
  842. $("#dgl2_refresh").css("cursor", "wait");
  843. return fillGroups(0);
  844. }).then(function() {
  845. // $("span.dgl2_descr").text("Add this deviation to one of your groups");
  846. displayModeText();
  847. }).catch(function(e) {
  848. if (e.ErrType != null) errorHndl(e);
  849. else errorHndl(err(errtyps.Unknown_Error, "fillGroups error", e));
  850. }).finally(function() {
  851. fetchingGroups = false;
  852. });
  853. }
  854. //templates
  855. function getGroupTemplate(name, img, id, latestDate = null) { //return HTML string
  856. return `<button title='${escapeHtml(name)}${(latestDate!=null)?"\nLast submission: "+(new Date(latestDate)).toLocaleString():""}' class='dgl2_groupButton' groupID=${id} type='group' groupName='${escapeHtml(name)}' activity='${latestDate==null?"unknown":((inactiveDate<new Date(latestDate))?"active":"inactive")}'>
  857. <div class='dgl2_imgwrap'>
  858. <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8' class='dgl2_hover'>"
  859. <path d='M4.75,3.25 L7,3.25 L7,4.75 L4.75,4.75 L4.75,7 L3.25,7 L3.25,4.75 L1,4.75 L1,3.25 L3.25,3.25 L3.25,1 L4.75,1 L4.75,3.25 Z'></path>
  860. </svg>
  861. <img class='dgl2_group_image' src='${img}'/>
  862. </div>
  863. <div class='dgl2_groupName'>${escapeHtml(name)}</div>
  864. </button>`;
  865. }
  866.  
  867. function getSubFolderOptionTemplate(name, devCnt, grID, foID, foType, img) {
  868. //text option only needs name,IDs and type
  869. return "<option class='dgl2_groupButton' groupID=" + grID + " folderName='" + escapeHtml(name) + "' folderID=" + foID + " folderType='" + foType + "' type='folder'>" + escapeHtml(name) + "</option>";
  870. }
  871.  
  872. function getSubFolderTemplate(name, devCnt, grID, foID, foType, img) { //return HTML string
  873. loadedFolders.set("" + foID, name);
  874. let imgstring;
  875. if (img.textContent) { //journal
  876. imgstring = "<p class='dgl2_journalSubF'>" + img.textContent.excerpt + "</p>";
  877. } else {
  878. let i;
  879. let cstr = "";
  880. img = img.media;
  881. for (i of img.types) {
  882. if (typeof i.c != "undefined") {
  883. cstr = i.c;
  884. break;
  885. }
  886. }
  887. if (cstr == "") {
  888. for (i of img.types) {
  889. if (typeof i.s != "undefined") {
  890. cstr = i.s;
  891. break;
  892. }
  893. }
  894. }
  895. if (img.baseUri) imgstring = img.baseUri;
  896. if (img.prettyName) imgstring += cstr.replace("<prettyName>", img.prettyName);
  897. if (img.token) imgstring += "?token=" + img.token[0];
  898. imgstring = "<img class='dgl2_group_image' title='" + escapeHtml(name) + "' src='" + imgstring + "'/>";
  899. }
  900.  
  901. return "<button class='dgl2_groupButton' groupID=" + grID + " folderName='" + escapeHtml(name) + "' folderID=" + foID + " folderType='" + foType + "' type='folder'>" +
  902. " <div class='dgl2_imgwrap'>" +
  903. " <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8' class='dgl2_hover'>" +
  904. " <path d='M4.75,3.25 L7,3.25 L7,4.75 L4.75,4.75 L4.75,7 L3.25,7 L3.25,4.75 L1,4.75 L1,3.25 L3.25,3.25 L3.25,1 L4.75,1 L4.75,3.25 Z'></path>" +
  905. " </svg>" +
  906. imgstring +
  907. " </div>" +
  908. " <div class='dgl2_groupName'>" + escapeHtml(name) + "</div>" +
  909. " <div class='dgl2_devCnt'>" + devCnt + "</div>" +
  910. "</button>";
  911. }
  912.  
  913. function getSearchBarTemplate() {
  914. return "<input id='dgl2_searchbar' type='text' placeholder='Search'/>";
  915. }
  916.  
  917. function getCollectionColTemplate() {
  918. return "<div id='dgl2_CollTab'><div class='dgl2_CollTitleBut'>" + svgTurnArrow + "<span class='dgl2_CollTitle'>" +
  919. "Collections</span></div><div class='buttons'></div><ul class='sortableList'></ul></div>";
  920. }
  921.  
  922. function getAddButTemplate() {
  923. let sty = getComputedStyle(document.body);
  924. return "<button title='Add group to collection/macro' class='dgl2_add'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172'>" +
  925. " <g style='stroke:" + sty.color + ";stroke-width:15;'>" +
  926. " <rect x='00' y='00' style='opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
  927. " <line x1='86' y1='30' x2='86' y2='142' />" +
  928. " <line x1='30' y1='86' x2='142' y2='86' />" +
  929. " </g>" +
  930. "</svg></button>";
  931. }
  932.  
  933. function getRecButTemplate() {
  934. let sty = getComputedStyle(document.body);
  935. return "<button title='Add groups to macro' class='dgl2_add'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172'>" +
  936. " <g style='stroke:" + sty.color + ";stroke-width:15;'>" +
  937. " <rect x='00' y='00' style='opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
  938. " <ellipse cx='86' cy='86' rx='40' ry='40'></ellipse>" +
  939. " </g>" +
  940. "</svg></button>";
  941. }
  942.  
  943. function getNewColTemplate() {
  944. let sty = getComputedStyle(document.body);
  945. return "<button title='New collection/macro' class='dgl2_new dgl2_topBut'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172'>" +
  946. " <g style='stroke:" + sty.color + ";stroke-width:15;'>" +
  947. " <rect x='00' y='00' style='opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
  948. " <line x1='86' y1='30' x2='86' y2='142' />" +
  949. " <line x1='30' y1='86' x2='142' y2='86' />" +
  950. " </g>" +
  951. "</svg></button>";
  952. }
  953.  
  954. function getSubButTemplate() {
  955. let sty = getComputedStyle(document.body);
  956. return "<button title='Remove groups from collection/macro' class='dgl2_sub'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172'>" +
  957. " <g style='stroke:" + sty.color + ";stroke-width:15;'>" +
  958. " <rect x='00' y='00' style='opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
  959. " <line x1='30' y1='86' x2='142' y2='86' />" +
  960. " </g>" +
  961. "</svg></button>";
  962. }
  963.  
  964. function getRefreshButTemplate() {
  965. let sty = getComputedStyle(document.body);
  966. return "<button title='refresh list of groups' id='dgl2_refresh'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172' style=' fill:#000000;'><g fill='none' fill-rule='nonzero' stroke='none' stroke-width='1' stroke-linecap='butt' stroke-linejoin='miter' stroke-miterlimit='10' stroke-dasharray='' stroke-dashoffset='0' font-family='none' font-weight='none' font-size='none' text-anchor='none' style='mix-blend-mode: normal'><path d='M0,172v-172h172v172z' fill='none'></path>" +
  967. " <linearGradient id='dgl2_grad1' x1='0%' y1='100%' x2='0%' y2='0%'><stop id='dgl2_grad1_stop1' offset='0%' style='stop-color:rgb(0,255,0);stop-opacity:1' /><stop id='dgl2_grad1_stop2' offset='100%' style='stop-color:rgb(255,0,0);stop-opacity:1' /></linearGradient>" +
  968. "<rect x='00' y='00' style='stroke:" + sty.color + ";stroke-width:5;opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
  969. "<g fill='" + sty.color + "'><path d='M62.00062,36.98l7.99979,10.89333h21.44625c18.10591,0 32.68,14.57409 32.68,32.68v21.78667h-16.34l21.78667,29.78646l21.78667,-29.78646h-16.34v-21.78667c0,-23.99937 -19.57396,-43.57333 -43.57333,-43.57333zM42.42667,39.87354l-21.78667,29.78646h16.34v21.78667c0,23.99938 19.57396,43.57333 43.57333,43.57333h29.44604l-7.99979,-10.89333h-21.44625c-18.10591,0 -32.68,-14.57409 -32.68,-32.68v-21.78667h16.34z'></path></g><path d='M43.86,172c-24.22321,0 -43.86,-19.63679 -43.86,-43.86v-84.28c0,-24.22321 19.63679,-43.86 43.86,-43.86h84.28c24.22321,0 43.86,19.63679 43.86,43.86v84.28c0,24.22321 -19.63679,43.86 -43.86,43.86z' fill='none'></path><path d='M47.3,168.56c-24.22321,0 -43.86,-19.63679 -43.86,-43.86v-77.4c0,-24.22321 19.63679,-43.86 43.86,-43.86h77.4c24.22321,0 43.86,19.63679 43.86,43.86v77.4c0,24.22321 -19.63679,43.86 -43.86,43.86z' fill='none'></path></g></svg></button";
  970. }
  971.  
  972. function getDelButTemplate() {
  973. let sty = getComputedStyle(document.body);
  974. return "<button title='Delete collection/macro' class='dgl2_del'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172'>" +
  975. "<g style='stroke-width:5;stroke:" + sty.color + ";fill:none'>" +
  976. " <rect x='00' y='00' style='opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
  977. " <rect x='50' y='50' rx='5' ry='5' width='72' height='92'></rect>" +
  978. " <rect x='65' y='35' rx='5' ry='5' width='42' height='15'></rect>" +
  979. " <line x1='40' y1='50' x2='132' y2='50'/>" +
  980. " <line x1='70' y1='132' x2='70' y2='60'/>" +
  981. " <line x1='86' y1='132' x2='86' y2='60' />" +
  982. " <line x1='104' y1='132' x2='104' y2='60' />" +
  983. " </g>" +
  984. "</svg></button>";
  985. }
  986.  
  987. function getExportButTemplate() {
  988. return '<button title="Export collection/macro list to file" class="dgl2_export dgl2_topBut"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 5.2916665 5.2916668">' +
  989. ' <g transform="translate(0,-291.70832)">' +
  990. ' <path style="fill:#008000;"' +
  991. ' d="M 0.26458332,291.9729 H 5.0270831 v 4.7625 H 0.79345641 l -0.52887309,-0.51217 z" />' +
  992. ' <rect style="fill:#ffffff;" width="3.7041667" height="1.8520833" x="0.79374999" y="292.23749" />' +
  993. ' <rect style="fill:#ffffff;" width="2.6458333" height="1.3229259" x="1.3229166" y="295.41248" />' +
  994. ' <rect style="fill:#008000;" width="0.52916676" height="0.79375702" x="2.9104166" y="295.67706" />' +
  995. ' </g>' +
  996. '</svg></button>';
  997. }
  998.  
  999. function getImportButTemplate() {
  1000. return '<button class="dgl2_import dgl2_topBut" title="Import collection/macro list from file" ><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 5.2916665 5.2916668">' +
  1001. ' <g transform="translate(0,-291.70832)">' +
  1002. ' <rect style="fill:#806600;" width="3.96875" height="2.9104137" x="0.52916664" y="293.03125" />' +
  1003. ' <path style="fill:#ffcc00;" d="m 0.52916666,295.94165 0.79375004,-2.11666 h 3.96875 l -0.7937501,2.11666 z" />' +
  1004. ' <rect style="fill:#00DD00;" width="0.52916664" height="1.0583333" x="3.4395833" y="292.50208" />' +
  1005. ' <path style="fill:#00DD00;" d="m 3.175,292.50207 0.5291667,-0.52917 0.5291667,0.52917 z" />' +
  1006. ' </g>' +
  1007. '</svg></button>';
  1008. }
  1009.  
  1010. function getTitleBarTemplate() {
  1011. return '<span class="dgl2_titleText">Add to Group</span>' +
  1012. '<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill-rule="evenodd" d="M8.84210526,13 L8.84210526,21.1578947 L2,21.1578947 L2,9.57894737 L12,3 L22,9.57894737 L22,21.1578947 L15.1578947,21.1578947 L15.1578947,13 L8.84210526,13 Z"></path></svg>' +
  1013. '<span class="dgl2_descr">Add this deviation to one of your groups</span>'
  1014. }
  1015.  
  1016. function getEditButTemplate() {
  1017. return '<button title="Change collection/macro name" class="dgl2_edit"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20.389 6.4503l-3-3-1.46-1.45-1.41 1.42-11.52 11.58-1 .97v6.03h5.987l1.013-1 11.41-11.76 1.39-1.41-1.41-1.38zm-4.45-1.62l3 3-.88.87-3-3 .88-.87zm.74 5.33l-8.21 8.2-2.801-3.0118 8.0028-8.099 3.0083 2.9108zm-12.68 9.84v-3.17l3.0433 3.17H3.9991z"></path></svg></button>';
  1018. }
  1019.  
  1020. function getVisibleButTemplate() {
  1021. return `<button title="Hide/show groups within collection" class="dgl2_visible">
  1022. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 250" stroke-width="20">
  1023. <defs>
  1024. <radialGradient id="c1" cx="0.5" cy="0.5" r="0.5">
  1025. <stop offset="0" stop-color="#ffffff" />
  1026. <stop offset=".5" stop-color="hsl(40, 60%, 60%)" />
  1027. <stop offset="1" stop-color="#3dff3d" />
  1028. </radialGradient>
  1029. </defs>
  1030. <ellipse role="sclera" cx="250" cy="125" rx="200" ry="100" fill="white"/>
  1031. <ellipse role="iris" cx="250" cy="125" rx="95" ry="95" stroke="black" fill="url(#c1)"/>
  1032. <ellipse role="pupil" cx="250" cy="125" rx="50" ry="50" stroke="none" fill="black"/>
  1033. <ellipse role="light" cx="200" cy="80" rx="50" ry="50" stroke="none" fill="#fffffaee"/>
  1034. <ellipse role="outline" cx="250" cy="125" rx="200" ry="100" stroke="black" fill="none"/>
  1035. </svg>
  1036. </button>`;
  1037. }
  1038.  
  1039. function addCss() {
  1040. if ($("#dgl2_style").length > 0) return;
  1041. let style = $("<style type='text/css' id='dgl2_style'></style>");
  1042.  
  1043. //searchbar
  1044. style.append("#dgl2_searchbar{background: var(--L3);box-shadow: inset 0 1px 4px 0 rgba(0,0,0,.25);padding: 5px;width: 50%;}");
  1045.  
  1046. //right collection column
  1047. style.append(`
  1048. #dgl2_CollTab{color: #2d3a2d;padding-left:15px; overflow-y: auto;font-family: CalibreSemiBold,sans-serif;font-weight: 600;font-size: 20px;font-display: swap;line-height: 24px;letter-spacing: .3px;margin-bottom: 28px;grid-row: 2;}
  1049. #dgl2_CollTab ul{overflow-wrap: anywhere;overflow: auto;list-style: none;padding-left: 10px;margin-top: 20px;}
  1050. #dgl2_CollTab ul li{cursor:pointer;padding:2px;display:grid;grid-template: auto/7px auto 16px 16px 16px 16px 16px;}
  1051. #dgl2_CollTab ul li:hover{background:linear-gradient(to right, rgba(255,0,0,0.1), rgba(255,0,0,0));}
  1052. #dgl2_CollTab button{cursor:pointer;border-width: 0;padding: 0;margin: 0;background-color: transparent;}
  1053. #dgl2_CollTab button:hover rect{fill:red;user-select: none; }
  1054. #dgl2_refresh{margin-left:auto;border-width:0px;background:transparent;cursor:pointer}
  1055. #dgl2_CollTab div.buttons{display: inline-block;vertical-align: middle;margin: 0 5px;}
  1056. div.dgl2_groupCol{overflow-y:auto;grid-row: 2;}
  1057. div.dgl2_groupdialog{display: grid;position: fixed;top: 50%;left: 50%;z-index:42;transform: translate(-50%,-50%);height: 80%;background-color:#afcaa9;grid-template-columns: auto 300px;grid-template-rows: 50px auto;width: 80%;border: 2px solid #2a5d00;border-radius: 10px;box-shadow: 1px 1px 2px ;}
  1058. div.dgl2_titlebar{cursor:move;display:flex;justify-content:space-between;grid-row: 1;grid-column: 1/3;background: linear-gradient(#619c32,#378201);color: white;align-items: center;}
  1059. div.dgl2_titlebar > * {margin: 7px;}
  1060. #dgl2_refresh:hover rect{fill:red;}
  1061. div.dgl2_closeDiag{border-radius: 50px;padding: 7px;cursor: pointer;}
  1062. #dgl2_refresh rect{fill:rgba(255,0,0,0.1);}
  1063. #dgl2_CollTab ul li span.handle{vertical-align:middle; display:inline-block; width:5px; height:100%; cursor:move; text-align:center;background-color:#363; background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABZJREFUeNpi2r9//38gYGAEESAAEGAAasgJOgzOKCoAAAAASUVORK5CYII=);}
  1064. button.dgl2_groupButton{border-radius:15px; background-color:rgba(255,255,255,0.5);margin:5px; padding:5px; width:120px; border-width:0px; overflow:hidden; position:relative; cursor:pointer; }
  1065. div.dgl2_imgwrap{ position: relative;}
  1066. svg.dgl2_hover{ position: absolute; left: 50%; width:50%; height:50%; transform: translate(-50%,50%); opacity:0; transition: ease 0.25s; }
  1067. img.dgl2_group_image{ opacity:1; width:100px; height:50px; transition: ease 0.25s; border-radius:2px; }
  1068. div.dgl2_groupName{ font-family: CalibreSemiBold; font-size: 15px; line-height: 15px; font-weight: 600; letter-spacing: 0.3px; word-break: break-word; }
  1069. button.dgl2_groupButton:hover{background-color:rgba(255,255,255,0.8);}
  1070. button.dgl2_groupButton:hover svg.dgl2_hover{opacity:1;}
  1071. button.dgl2_groupButton:hover img.dgl2_group_image{opacity:0.3;}
  1072. button.dgl2_groupButton:active{background-color:rgba(255,255,255,0.3);}
  1073. button.dgl2_groupButton.dgl2_inGroup{background-image:linear-gradient(red, transparent);}
  1074. span.dgl2_titleText{cursor:pointer;}
  1075. span.dgl2_descr{font-family: CalibreRegular,sans-serif; font-weight: 400; font-size: 13px; font-display: swap; letter-spacing: 1.3px; margin-left: 32px; text-transform: uppercase;}
  1076. button.dgl2_edit{height:0.5em;}
  1077. button.dgl2_edit:hover path{fill:red;}
  1078. #dgl2_CollTab button svg{width: 90%;}
  1079. #dgl2_CollTab button.dgl2_visible:hover ellipse{stroke: red;}
  1080. div.dgl2_addGroup{background-color: rgba(0, 255, 0, 0.3);}
  1081. div.dgl2_remGroup{background-color: rgba(255, 0, 0, 0.3);}
  1082. button.dgl2_inCollection{background-color: rgba(15, 104, 5, 0.7);}
  1083. button.dgl2_export:hover ,button.dgl2_import:hover {opacity:0.8}
  1084. button.dgl2_export:active ,button.dgl2_import:active {opacity:1}
  1085. div.dgl2_CollTitleBut{cursor:pointer;display:inline-block;}
  1086. div.dgl2_CollTitleBut:hover span{color:red;}
  1087. .dgl2_journalSubF { overflow: hidden; height: 50px; font-size: xx-small; text-align: left; margin-bottom: 5px;}
  1088. .groupPopup .ui-widget-content{background-color:#afcaa9 !important;color:black;}
  1089. button.dgl2_letterfound {background-color: rgba(105, 14, 5, 0.7);}
  1090. .folderInMacro {background-color:rgba(205, 24, 25, 0.6)!important;}
  1091. @keyframes shadowPulse {0% {box-shadow: 0px 0px 50px 20px #f00;} 100% {box-shadow: 0px 0px 50px 20px #ff000000;}}
  1092. .shadow-pulse {animation-name: shadowPulse;animation-duration: 0.5s;animation-iteration-count: infinite;animation-timing-function: linear; animation-direction:alternate;}
  1093. #dgl2_grContext{display: none;z-index: 1000;position: absolute;overflow: hidden;white-space: nowrap;padding: 5px;background-color: #afcaa9;border-radius: 5px;border: 2px solid green;}
  1094. #dgl2_grContext select{background: none; border: none;width:100%;margin:5px 0;}
  1095. #dgl2_grContext select option{background-color: #ddffd8;}
  1096. #dgl2_grContext select option:nth-child(even) {background-color: #6fd061;}
  1097. #dgl2_grContext select option::selection {color: red;background: yellow;}
  1098. #dgl2_grContext button {cursor:pointer; width: 100%;background-color: #408706;color: white;border: 1px outset black;border-radius: 5px;}
  1099. #dgl2_grContext button:hover { background-color: #608706;}
  1100. #dgl2_CollTab li[active='0'] button.dgl2_visible ellipse[role='iris'] { fill: lightgray;stroke:lightgray}
  1101. #dgl2_CollTab li[active='0'] button.dgl2_visible ellipse { fill: lightgray;}
  1102. #dgl2_CollTab li button{display: flex; height: 100%;align-items: center;}
  1103. #dgl2_CollTab li.dgl2_drgover{border-top: 2px solid blue;}
  1104. #dgl2_alertBox {color: #2d3a2d; z-index:7777;box-shadow: 1px 1px 2px black;position: fixed;top: 50%;left: 50%;background-color: #afcaa9;border-radius: 5px;border: 2px solid #285c00;transform: translate(-50%, -50%);}
  1105. #dgl2_alertBox .dgl2_alertTitle{cursor:move;background-color:#5d982d;color:white;font-weight: bold;}
  1106. #dgl2_alertBox div.dgl2_alertButtons div{padding: 5px;display: inline-block;border-radius: 5px;border: 1px solid;cursor: pointer;margin:5px;}
  1107. #dgl2_alertBox .dgl2_alertOKBut{background-color: #d0e8cb;}
  1108. #dgl2_alertBox .dgl2_alertCancelBut{background-color: #e8e3cb;}
  1109. #dgl2_alertBox>div{padding:5px;}
  1110. #dgl2_alertBox>div{padding:5px;}
  1111. #dgl2_alertBox div.dgl2_alertButtons{display: flex;flex-direction: row-reverse;}
  1112. #dgl2_promptVal{display: block;margin: 5px;border-radius: 5px;width: 90%;}
  1113. button.dgl2_groupButton[activity="inactive"]{background-color:#666;}
  1114. `);
  1115.  
  1116. // style.append(".noTitleDialog .ui-dialog-titlebar {display:none}");
  1117.  
  1118. $("head").append(style);
  1119.  
  1120. // $("head").append(
  1121. // '<link ' +
  1122. // 'href="//ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/le-frog/jquery-ui.min.css" ' +
  1123. // 'rel="stylesheet" type="text/css">'
  1124. // );
  1125.  
  1126. }
  1127.  
  1128. //function from https://www.w3schools.com/howto/howto_js_draggable.asp
  1129. //makes elements draggable
  1130. function dragElement(elmnt) {
  1131. let pos1 = 0,
  1132. pos2 = 0,
  1133. pos3 = 0,
  1134. pos4 = 0;
  1135. if (elmnt.querySelector(".dgl2_alertTitle")) {
  1136. // if present, the header is where you move the DIV from:
  1137. elmnt.querySelector(".dgl2_alertTitle").onmousedown = dragMouseDown;
  1138. }
  1139. if (elmnt.querySelector(".dgl2_titlebar")) {
  1140. // if present, the header is where you move the DIV from:
  1141. elmnt.querySelector(".dgl2_titlebar").onmousedown = dragMouseDown;
  1142. } else {
  1143. // otherwise, move the DIV from anywhere inside the DIV:
  1144. elmnt.onmousedown = dragMouseDown;
  1145. }
  1146.  
  1147. function dragMouseDown(e) {
  1148. e = e || window.event;
  1149. if (e.target.tagName == "INPUT") return;
  1150.  
  1151. e.preventDefault();
  1152. // get the mouse cursor position at startup:
  1153. pos3 = e.clientX;
  1154. pos4 = e.clientY;
  1155. document.onmouseup = closeDragElement;
  1156. // call a function whenever the cursor moves:
  1157. document.onmousemove = elementDrag;
  1158. }
  1159.  
  1160. function elementDrag(e) {
  1161. e = e || window.event;
  1162. e.preventDefault();
  1163. // calculate the new cursor position:
  1164. pos1 = pos3 - e.clientX;
  1165. pos2 = pos4 - e.clientY;
  1166. pos3 = e.clientX;
  1167. pos4 = e.clientY;
  1168. // set the element's new position:
  1169. elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
  1170. elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
  1171. }
  1172.  
  1173. function closeDragElement() {
  1174. // stop moving when mouse button is released:
  1175. document.onmouseup = null;
  1176. document.onmousemove = null;
  1177. }
  1178. }
  1179. //filling GUI DOM
  1180. function hideHiddenGroups() {
  1181. collections.forEach(col => {
  1182. if (col.showing == 0) {
  1183. col.groups.forEach(grID => {
  1184. $("button[groupID='" + grID + "']").hide();
  1185. });
  1186. }
  1187. });
  1188. }
  1189.  
  1190. function insertFilteredGroups(id) {
  1191. insertGroups();
  1192. lastFilter = id;
  1193. let allButs = $("button[type='group']");
  1194. if (collections[id].groups.length == 0) {
  1195. allButs.show();
  1196. } else {
  1197. allButs.hide();
  1198. for (let grID of collections[id].groups) {
  1199. $("button[groupID='" + grID + "']").show();
  1200. }
  1201. }
  1202. hideHiddenGroups()
  1203. }
  1204.  
  1205. function insertMacroGroups(id) {
  1206. insertGroups();
  1207. lastFilter = id;
  1208. let allButs = $("button[type='group']");
  1209. if (macros[id].data.length == 0) {
  1210. allButs.show();
  1211. } else {
  1212. allButs.hide();
  1213. for (let grID of macros[id].data) {
  1214. $("button[groupID='" + grID.groupID + "']").show();
  1215. }
  1216. }
  1217. }
  1218.  
  1219. function insertMacros() {
  1220. let coltab = 0;
  1221. let toAffect = $("div.dgl2_groupdialog").not("[dgl2]").attr("dgl2", 1);
  1222. if (toAffect.length > 0) {
  1223. coltab = $(getCollectionColTemplate());
  1224. toAffect.append(coltab);
  1225. coltab.find("div.buttons")
  1226. .append(getNewColTemplate())
  1227. .append(getExportButTemplate())
  1228. .append(getImportButTemplate());
  1229.  
  1230. coltab.click(Ev_colListClick);
  1231. } else {
  1232. coltab = $("#dgl2_CollTab");
  1233. }
  1234. let colList = coltab.find("ul");
  1235. colList.empty();
  1236. let el, descr;
  1237. for (let col of macros) {
  1238. descr = "";
  1239. for (let gr of col.data) {
  1240. descr += groupNameById(gr.groupID) + "/" + gr.folderName + "\n";
  1241. }
  1242. el = "<li colid=" + col.id + " title='" + escapeHtml(descr) + "' id='dgl2item-" + col.id + "'><span class='handle'></span><span>" + col.name + "</span>" + getEditButTemplate();
  1243. el += getRecButTemplate() + getSubButTemplate() + getDelButTemplate();
  1244. el += "</li>";
  1245. colList.append(el);
  1246. }
  1247. if (macroOrder.length > 0) {
  1248. $.each(macroOrder, function(i, position) {
  1249. let $target = colList.find('#' + position);
  1250. $target.appendTo(colList); // or prependTo for reverse
  1251. });
  1252. }
  1253.  
  1254.  
  1255. makesortable();
  1256. }
  1257.  
  1258. function insertCollections() {
  1259. let coltab = 0;
  1260. let toAffect = $("div.dgl2_groupdialog").not("[dgl2]").attr("dgl2", 1); //group submission container
  1261. if (toAffect.length > 0) {
  1262. coltab = $(getCollectionColTemplate());
  1263. toAffect.append(coltab);
  1264. coltab.find("div.buttons")
  1265. .append(getNewColTemplate())
  1266. .append(getExportButTemplate())
  1267. .append(getImportButTemplate());
  1268.  
  1269. coltab.click(Ev_colListClick);
  1270. } else {
  1271. coltab = $("#dgl2_CollTab");
  1272. }
  1273.  
  1274. let colList = coltab.find("ul");
  1275. colList.empty();
  1276. let el;
  1277. for (let col of collections) {
  1278. el = `<li colid=${col.id} active='${col.showing}' id='dgl2item-${col.id}'><span class='handle'></span><span>${col.name}</span>${getEditButTemplate()}`;
  1279. if (col.id > 0) el += getVisibleButTemplate() + getAddButTemplate() + getSubButTemplate() + getDelButTemplate();
  1280. el += "</li>";
  1281. colList.append(el);
  1282. }
  1283. if (collectionOrder.length > 0) {
  1284. $.each(collectionOrder, function(i, position) {
  1285. let $target = colList.find('#' + position);
  1286. $target.appendTo(colList); // or prependTo for reverse
  1287. });
  1288. }
  1289.  
  1290. makesortable();
  1291. }
  1292.  
  1293. function makesortable() {
  1294. //ul.sortableList
  1295. //li colid=col.id id='dgl2item-" + col.id,
  1296. //<span class='handle'>
  1297.  
  1298. let lists = document.querySelectorAll("ul.sortableList li");
  1299.  
  1300. for (let i = 0; i < lists.length; ++i) {
  1301. lists[i].draggable = "true";
  1302. addDragHandler(lists[i]);
  1303. }
  1304.  
  1305. }
  1306.  
  1307. //drag handler for drag-sortable lists
  1308. function addDragHandler(entry) {
  1309. entry.addEventListener('dragstart', function(e) {
  1310. e.dataTransfer.setData('text/plain', this.id);
  1311. e.dataTransfer.effectAllowed = 'move';
  1312. }, false);
  1313. // entry.addEventListener('dragenter', function(e){}, false)
  1314. entry.addEventListener('dragover', function(e) {
  1315. if (e.preventDefault) {
  1316. e.preventDefault();
  1317. }
  1318. this.classList.add('dgl2_drgover');
  1319. e.dataTransfer.dropEffect = 'move'; // See the section on the DataTransfer object.
  1320. return false;
  1321. }, false);
  1322. entry.addEventListener('dragleave', function(e) {
  1323. this.classList.remove('dgl2_drgover');
  1324. }, false);
  1325. entry.addEventListener('drop', function(e) {
  1326. var dropHTML = e.dataTransfer.getData('text/plain');
  1327. this.parentNode.insertBefore(document.querySelector("#" + dropHTML), this);
  1328. this.classList.remove('dgl2_drgover');
  1329.  
  1330. if (colListMode == 0) {
  1331. collectionOrder = [...this.parentNode.querySelectorAll("[draggable]")].map(el => el.id);
  1332. GM.setValue("collectionOrder", JSON.stringify(collectionOrder));
  1333. } else if (colListMode == 1) {
  1334. macroOrder = [...this.parentNode.querySelectorAll("[draggable]")].map(el => el.id);
  1335. GM.setValue("macroOrder", JSON.stringify(macroOrder));
  1336. }
  1337. return false;
  1338. }, false);
  1339. entry.addEventListener('dragend', function(e) {
  1340. this.classList.remove('dgl2_drgover');
  1341. }, false);
  1342. }
  1343.  
  1344. function insertSearchBar() {
  1345. let bar = $(getSearchBarTemplate());
  1346. let refrBut = $(getRefreshButTemplate());
  1347.  
  1348. $("div.dgl2_titlebar").append(refrBut).append(bar).append(
  1349. $("<div class='dgl2_closeDiag'>X</div>").click(function() {
  1350. $("div.dgl2_groupdialog").hide();
  1351. })
  1352. );
  1353.  
  1354. bar.keyup(function() {
  1355. let search = $(this).val();
  1356. let allButs = $("button[type='group']").show();
  1357. allButs.filter(function() {
  1358. if (lastFilter > 1 && collections[lastFilter].groups.indexOf($(this).attr("groupID")) == -1) return 1;
  1359. if (search == "") return 0;
  1360. let words = search.split(" ");
  1361. for (let word of words) {
  1362. if ($(this).attr('groupName').toLowerCase().indexOf(word) == -1) return 1;
  1363. }
  1364. return 0;
  1365. }).hide();
  1366. });
  1367. //bar.mousedown(function(event){event.stopPropagation(); event.preventDefault();event.target.focus();});
  1368.  
  1369. refrBut.click(Ev_getGroupClick);
  1370.  
  1371. $("span.dgl2_titleText").click(function() {
  1372. if (colListMode == 0) {
  1373. insertGroups();
  1374. $("li[colid=" + lastFilter + "]").click();
  1375.  
  1376. let lastgrBut = $("button[groupid=" + lastGroupClickID + "]");
  1377. lastgrBut[0].scrollIntoView();
  1378. lastgrBut.addClass("shadow-pulse");
  1379. } else {
  1380. macroMode = 0; //abort macro mode add/remove
  1381. insertGroups();
  1382. displayModeText();
  1383. $("div.dgl2_groupWrapper").removeClass("dgl2_addGroup").removeClass("dgl2_remGroup");
  1384. $("button.dgl2_inCollection").removeClass("dgl2_inCollection");
  1385. }
  1386. });
  1387. }
  1388.  
  1389. // function pulsing(element) { not used
  1390. // element.animate({ opacity: 0 }, 250, function() {
  1391. // $(this).animate({ opacity: 1 }, 250, pulsing);
  1392. // });
  1393. // }
  1394.  
  1395. function insertSubFolders(subfolders) { //fill view with subfolders //subfolders not stored, request when needed
  1396. let buts = $("button.dgl2_groupButton"); //button wrapper
  1397. subfolders.sort(function(l, u) {
  1398. return l.name.toLowerCase().localeCompare(u.name.toLowerCase());
  1399. });
  1400.  
  1401. if (buts.length > 0) {
  1402. let subf;
  1403. let newBut;
  1404. if ($("#dgl2_grContext").is(":visible")) {
  1405. let cont = $("#dgl2_grContext select");
  1406. cont.empty();
  1407. for (subf of subfolders) {
  1408. if (subf.thumb == null) continue; //no thumb=db problem, so also no text entry
  1409. newBut = $(getSubFolderOptionTemplate(subf.name, subf.size, subf.owner.userId, subf.folderId, subf.type, subf.thumb));
  1410. cont.append(newBut);
  1411. }
  1412. if (subfolders.length == 0) {
  1413. $("#dgl2_grContext span.desc").html("This group does<br/>not allow submissions<br/>using the gallery<br/>system!");
  1414. } else {
  1415. $("#dgl2_grContext span.desc").text("Submit to a Folder");
  1416. }
  1417.  
  1418. } else {
  1419. let par = buts.first().parent();
  1420. par.empty();
  1421. for (subf of subfolders) {
  1422. if (subf.thumb == null) continue;
  1423. newBut = $(getSubFolderTemplate(subf.name, subf.size, subf.owner.userId, subf.folderId, subf.type, subf.thumb));
  1424. par.append(newBut);
  1425. }
  1426. if (subfolders.length == 0) {
  1427. par.append("This group does not allow submissions using the gallery system!");
  1428. }
  1429. par.not("[dgl2]").attr("dgl2", 1).click(Ev_groupClick);
  1430. }
  1431. }
  1432. }
  1433.  
  1434. function displayModeText() {
  1435. let titl = "Add to Group";
  1436. let descr = "Add this deviation to one of your groups";
  1437. if (targetName != "") {
  1438. if (colListMode == 0) { //colection
  1439. if (colMode == 0) { //show
  1440. titl = "< Add to " + targetName;
  1441. descr = "Add this deviation to Group " + targetName;
  1442. } else if (colMode == 1) { //add
  1443. titl = "< Add to " + targetName;
  1444. descr = "Add groups to Collection " + targetName;
  1445. } else if (colMode == 2) { //remove
  1446. titl = "< Remove from " + targetName;
  1447. descr = "Remove groups from Collection " + targetName;
  1448. }
  1449. } else { //macros
  1450. if (macroMode == 0) {} else if (macroMode == 1) {
  1451. titl = "< Add to Macro";
  1452. descr = "macro " + targetName + " is recording.";
  1453. } else if (macroMode == 3) { //remove
  1454. titl = "< Remove from Macro";
  1455. descr = "remove groups from " + targetName;
  1456. }
  1457. }
  1458. }
  1459. $("span.dgl2_titleText").text(titl);
  1460. $("span.dgl2_descr").text(descr);
  1461. }
  1462.  
  1463. function insertGroups() { //fill view with groups //groups are stored
  1464. lastFilter = 0;
  1465. groups.sort(function(l, u) {
  1466. return l.username.toLowerCase().localeCompare(u.username.toLowerCase());
  1467. });
  1468.  
  1469. displayModeText();
  1470.  
  1471. let par = $("div.dgl2_groupWrapper"); //group list wrapper
  1472. let newBut;
  1473. let hasEmptyId = false;
  1474. par.empty();
  1475. for (let gr of groups) {
  1476. newBut = $(getGroupTemplate(gr.username, gr.usericon, gr.userId, gr.latestDate));
  1477. newBut.contextmenu(Ev_groupContext);
  1478. if (typeof gr.userId == "undefined") hasEmptyId = true;
  1479. if (listedGroups.includes(parseInt(gr.userId))) {
  1480. newBut.addClass("dgl2_inGroup"); //button div inside wrapper; used in template
  1481. }
  1482. if (par.find("div[groupName='" + gr.username + "']").length == 0) {
  1483. par.append(newBut);
  1484. }
  1485. }
  1486. par.not("[dgl2]").attr("dgl2", 1).click(Ev_groupClick);
  1487. hideHiddenGroups();
  1488.  
  1489. if (hasEmptyId && !notScanThisInstance) {
  1490. requestAllGroupIDs();
  1491. }
  1492. }
  1493.  
  1494. function uniqBy(a, key) {
  1495. let seen = new Set();
  1496. return a.filter(item => {
  1497. let k = key(item);
  1498. return seen.has(k) ? false : seen.add(k);
  1499. });
  1500. }
  1501. let ngrpCnt = 0;
  1502. let fetchItMsg = ""
  1503. let ngrpleft = 0;
  1504.  
  1505. function delayIteratefetchGrId(groups, index, delay) {
  1506. if (index < groups.length) {
  1507. grabIDfromPage(groups[index].username).catch(() => {
  1508. if (fetchItMsg != "") fetchItMsg += ", ";
  1509. fetchItMsg += groups[index].username;
  1510. groups[index].userId = 0;
  1511. groups[index].useridUuid = 0;
  1512. GM.setValue("groups", JSON.stringify(groups));
  1513. }).finally(() => {
  1514. setTimeout(function() { delayIteratefetchGrId(groups, index + 1, delay); }, delay);
  1515. });
  1516. } else {
  1517. if (fetchItMsg != "") myMsgBox("failed fetching IDs for groups: " + fetchItMsg + "<br/>They might be deleted.")
  1518. $("#dgl2_refresh rect").css("fill", "");
  1519. $("#dgl2_refresh").css("cursor", "pointer");
  1520. //$("span.dgl2_descr").text("Add this deviation to a group folder");
  1521. displayModeText();
  1522. }
  1523. }
  1524.  
  1525. function requestAllGroupIDs() {
  1526. let nullgr = groups.filter(gr => typeof gr.userId == "undefined");
  1527. let remtim = nullgr.length * 1.1;
  1528. let remtex = ""
  1529. ngrpleft = nullgr.length;
  1530. if (remtim < 60) { remtex = "seconds" } else if (remtim >= 60 && remtim < 60 * 60) {
  1531. remtex = "minutes";
  1532. remtim /= 60.0;
  1533. } else if (remtim >= 60 * 60) {
  1534. remtex = "hours";
  1535. remtim /= 60.0 * 60.0;
  1536. }
  1537. myMsgBox(`${ngrpleft} group-IDs are not fetched.<br/>Fetching all group IDs now might take a while (est. ${Math.round((remtim + Number.EPSILON) * 100) / 100} ${remtex}).<br/>If you press "cancel" IDs are fetched dynamically when group-buttons are clicked.<br/>Collections, macros and list of already submitted deviations can not display groups without ID.<br/><br/>Fetch all remaining group-IDs now?`, "Fetch Group-IDs", 1).then((choice) => {
  1538. if (choice) {
  1539. ngrpCnt = 0;
  1540. fetchItMsg = "";
  1541. delayIteratefetchGrId(nullgr, 0, 250);
  1542. }
  1543. });
  1544. notScanThisInstance = true;
  1545. }
  1546.  
  1547. function insertHTML() {
  1548.  
  1549. if ($("div.dgl2_groupdialog").length > 0) return;
  1550.  
  1551. addCss();
  1552. $("<div class='dgl2_groupdialog'><div class='dgl2_titlebar'></div><div class='dgl2_groupCol'><div class='dgl2_groupWrapper'></div></div></div>").appendTo($("body"));
  1553. $("div.dgl2_titlebar").html(getTitleBarTemplate());
  1554. insertSearchBar();
  1555.  
  1556. let devInd = location.href.indexOf("?");
  1557. if (devInd == -1) {
  1558. devID = location.href.match(/(\d+)\D*$/)[1];
  1559. } else {
  1560. devID = location.href.substring(0, devInd).match(/(\d+)\D*$/)[1];
  1561. }
  1562.  
  1563. userName = $("a.user-link").attr("data-username"); // "dediggefedde";
  1564. userId = $("a.user-link").attr("data-userid"); // "dediggefedde";
  1565.  
  1566. let proms = [
  1567. GM.getValue("collections", ""),
  1568. GM.getValue("collectionOrder", ""),
  1569. GM.getValue("macros", ""),
  1570. GM.getValue("macroOrder", "")
  1571. ];
  1572.  
  1573. Promise.all(proms).then(([cols, colOrder, macs, macOrder]) => {
  1574. if (cols != "") {
  1575. collections = JSON.parse(cols);
  1576. }
  1577. collections.forEach(el => { if (!el.hasOwnProperty("showing")) { el.showing = 1; }; }); //backward-compatibility for collection-showing attribute before v3.0
  1578.  
  1579. if (colOrder != "") {
  1580. collectionOrder = JSON.parse(colOrder);
  1581. }
  1582.  
  1583. if (macs != "") {
  1584. macros = JSON.parse(macs);
  1585. macros.forEach(function(el) { el.data = uniqBy(el.data, JSON.stringify); }); //unique macros
  1586. }
  1587.  
  1588. if (macOrder != "") {
  1589. macroOrder = JSON.parse(macOrder);
  1590. }
  1591. insertCollections();
  1592. }).catch(function(e) {
  1593. errorHndl(err(errtyps.Unknown_Error, "Error Loading Database", e));
  1594. return insertCollections();
  1595. });
  1596.  
  1597. GM.getValue("groups", "").then(function(grps) {
  1598. if (grps == "") {
  1599. Ev_getGroupClick();
  1600. } else {
  1601. groups = JSON.parse(grps);
  1602. insertGroups();
  1603. }
  1604. return fillListedGroups(devID,"");
  1605. }).then(()=>{
  1606. listedGroups.forEach(grId=>{
  1607. let grbut=document.querySelector("button.dgl2_groupButton[groupid='"+grId+"']");
  1608. if(grbut!=null)grbut.classList.add("dgl2_inGroup");
  1609. });
  1610. }).catch(function(e) {
  1611. errorHndl(err(errtyps.Unknown_Error, "fillListedGroups error", e));
  1612. });
  1613.  
  1614. }
  1615.  
  1616. function getLowestFree(collection) {
  1617. collection.sort(function(a, b) {
  1618. return a.id - b.id;
  1619. }); //changing order does not matter thanks to index/order array
  1620. let lowest = -1;
  1621. let i;
  1622. for (i = 0; i < collection.length; ++i) {
  1623. if (collection[i].id != i) {
  1624. lowest = i;
  1625. break;
  1626. }
  1627. }
  1628. if (lowest == -1 && collection.length > 0) {
  1629. lowest = collection[collection.length - 1].id + 1;
  1630. } else if (collection.length == 0) lowest = 0;
  1631. return lowest;
  1632.  
  1633. }
  1634.  
  1635. function escapeHtml(string) {
  1636. return String(string).replace(/[&<>"'`=\/]/g, function(s) {
  1637. return entityMap[s];
  1638. });
  1639. }
  1640.  
  1641. function download(data, filename) {
  1642. let file = new Blob([data], {
  1643. type: "application/json"
  1644. });
  1645. let a = document.createElement("a"),
  1646. url = URL.createObjectURL(file);
  1647. a.href = url;
  1648. a.download = filename;
  1649. document.body.appendChild(a);
  1650. a.click();
  1651. setTimeout(function() {
  1652. document.body.removeChild(a);
  1653. window.URL.revokeObjectURL(url);
  1654. }, 0);
  1655. }
  1656.  
  1657. function upload() {
  1658. return new Promise(function(resolve, reject) {
  1659. let inp = $('<input type="file" id="input">').appendTo("body").click()
  1660. inp.change(function() {
  1661. let reader = new FileReader();
  1662. reader.onload = function(evt) {
  1663. resolve(evt.target.result);
  1664. };
  1665. reader.readAsBinaryString($(this).prop("files")[0]);
  1666. });
  1667. return "";
  1668. });
  1669. }
  1670.  
  1671. function colIndexById(id) {
  1672. for (let i = 0; i < collections.length; ++i) {
  1673. if (collections[i].id == id) return i;
  1674. }
  1675. return -1;
  1676. }
  1677.  
  1678. function makIndexById(id) {
  1679. for (let i = 0; i < macros.length; ++i) {
  1680. if (macros[i].id == id) return i;
  1681. }
  1682. return -1;
  1683. }
  1684.  
  1685. //shows an alert box with text
  1686. //mode 0:alert, 1:confirm, 2 prompt
  1687. function myMsgBox(tex, titl = "Notification", mode = 0, defText = "") {
  1688. let dfd = new $.Deferred();
  1689.  
  1690. let box = $("#dgl2_alertBox");
  1691. if (box.length == 0) {
  1692. box = $("<div id='dgl2_alertBox'></div>").appendTo("body");
  1693. }
  1694.  
  1695. box.html("<div class='dgl2_alertTitle'></div><div class='dgl2_alertText'></div><div class='dgl2_alertButtons'></div>");
  1696. box.find("div.dgl2_alertText").html(tex);
  1697. box.find("div.dgl2_alertTitle").html(titl);
  1698.  
  1699. if (mode == 0) {
  1700. $("<div class='dgl2_alertOKBut'>OK</div>").click(function() {
  1701. dfd.resolve(true);
  1702. $("#dgl2_alertBox").hide();
  1703. }).appendTo(box.find("div.dgl2_alertButtons"));
  1704. } else if (mode == 1) {
  1705. $("<div class='dgl2_alertOKBut'>OK</div>").click(function() {
  1706. dfd.resolve(true);
  1707. $("#dgl2_alertBox").hide();
  1708. }).appendTo(box.find("div.dgl2_alertButtons"));
  1709. $("<div class='dgl2_alertCancelBut'>Cancel</div>").click(function() {
  1710. dfd.resolve(false);
  1711. $("#dgl2_alertBox").hide();
  1712. }).appendTo(box.find("div.dgl2_alertButtons"));
  1713. } else if (mode == 2) {
  1714. $("<input type='text' id='dgl2_promptVal' value='" + defText + "' class='text ui-widget-content ui-corner-all'>").appendTo(box.find("div.dgl2_alertText"));
  1715. $("<div class='dgl2_alertOKBut'>OK</div>").click(function() {
  1716. dfd.resolve($("#dgl2_promptVal").val());
  1717. $("#dgl2_alertBox").hide();
  1718. }).appendTo(box.find("div.dgl2_alertButtons"));
  1719. $("<div class='dgl2_alertCancelBut'>Cancel</div>").click(function() {
  1720. dfd.resolve(false);
  1721. $("#dgl2_alertBox").hide();
  1722. }).appendTo(box.find("div.dgl2_alertButtons"));
  1723. }
  1724. box.show();
  1725.  
  1726. dragElement(box[0]);
  1727.  
  1728. return dfd.promise();
  1729. }
  1730.  
  1731. function showPopup(event) {
  1732. event.preventDefault();
  1733. event.stopPropagation();
  1734. insertHTML(); //does nothing if already inserted
  1735.  
  1736. let el = $("div.dgl2_groupdialog");
  1737.  
  1738. el.show();
  1739. dragElement(el[0]);
  1740. el.attr("tabindex", "0");
  1741. el.keydown(function(event) {
  1742. if (event.target.tagName != "INPUT") {
  1743. highlightLetter(String.fromCharCode(event.which));
  1744. }
  1745. });
  1746. $("div.dgl2_groupdialog, div.dgl2_groupdialog>div").click(function(event) {
  1747. event.stopPropagation();
  1748. if (event.target.tagName != "INPUT") {
  1749. $("div.dgl2_groupdialog").focus();
  1750. }
  1751. });
  1752. }
  1753.  
  1754. function groupNameById(id) {
  1755. for (let g of groups) {
  1756. if (g.userId == id) return g.username;
  1757. }
  1758. return "";
  1759. }
  1760. //bind script to buttons. dynamic browsing compatible
  1761. function addListener() {
  1762. let els = $("div:nth-child(2) > span:nth-child(1) > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) > button:nth-child(1):not([dgl2='1'])");//svg button
  1763. //$('*[datahook="group_counter"]:not([dgl2=1]),*[data-hook="group_counter"]:not([dgl2=1])');
  1764. els.attr("dgl2", 1).find("svg").html(
  1765. '<path d="M18.63 17l1.89 5h2l-2.53-7h-6.67l.64 2zM4.04 15l-2.52 7h2l1.88-5h4.23l1.89 5h2l-2.53-7zM7.52 4.33c1.9304.011 3.4873 1.5829 3.48 3.5133-.0074 1.9303-1.5762 3.4903-3.5066 3.4866C5.563 11.3263 4 9.7604 4 7.83c0-1.933 1.567-3.5 3.5-3.5h.02zm-.02-2C4.4624 2.33 2 4.7924 2 7.83s2.4624 5.5 5.5 5.5 5.5-2.4624 5.5-5.5-2.4624-5.5-5.5-5.5zM13 3.37a5.59 5.59 0 0 1 1.5 1.45 3.41 3.41 0 0 1 1.5-.35c1.933 0 3.5 1.567 3.5 3.5s-1.567 3.5-3.5 3.5a3.41 3.41 0 0 1-1.5-.35 5.63 5.63 0 0 1-1.5 1.46c1.968 1.2806 4.532 1.1706 6.3831-.2738 1.8511-1.4445 2.5812-3.9047 1.8175-6.125C20.437 3.9608 18.348 2.4702 16 2.47a5.4102 5.4102 0 0 0-3 .9z"/>' +
  1766. '<path stroke="#0A0" stroke-width="4" stroke-opacity="0.8" d="M12 18H24M18 12V24"/>'
  1767. );
  1768. els.parent().parent().click(showPopup);
  1769. }
  1770. $(document).ready(function() {
  1771. $(document).mousedown(function(event) {
  1772. if ($(event.target).closest("#dgl2_grContext").length == 0) {
  1773. $("#dgl2_grContext").hide().find("select").empty();
  1774. }
  1775. });
  1776. });
  1777.  
  1778. setInterval(addListener, 1000);
  1779.  
  1780. })();