Map-Making Shortcuts

use shortcut to help you mapping on map-making app

  1. // ==UserScript==
  2. // @name Map-Making Shortcuts
  3. // @namespace https://greasyfork.org/users/1179204
  4. // @description use shortcut to help you mapping on map-making app
  5. // @version 1.3.7
  6. // @license BSD
  7. // @author KaKa
  8. // @match *://map-making.app/maps/*
  9. // @grant GM_addStyle
  10. // @icon https://www.svgrepo.com/show/521871/switch.svg
  11. // ==/UserScript==
  12. (function(){
  13. /* ------------------------------------------------------------------------------- */
  14. /* ----- KEYBOARD SHORTCUTS (MUST Refresh PAGE FOR CHANGES TO TAKE EFFECT) -------- */
  15. /* ------------------------------------------------------------------------------- */
  16.  
  17.  
  18. const KEYBOARD_SHORTCUTS = {
  19. // Single key
  20. switchLoc: 'Q',
  21.  
  22. rewindLoc: 'E',
  23.  
  24. deleteLoc: 'C',
  25.  
  26. closeLoc: 'V',
  27.  
  28. copyLoc:'G',
  29.  
  30. hideElement:'H',
  31.  
  32. deSelectAll:'Z',
  33.  
  34. pruneDuplicates:'P',
  35.  
  36.  
  37. // SHIFT key is need
  38. deleteTags:'B',
  39.  
  40. resetGulf:'M',
  41.  
  42. classicMap:'N',
  43.  
  44. findLinkPanos:'K',
  45.  
  46. exportAsCsv: 'D',
  47.  
  48. fullScreenMap:'F',
  49. };
  50.  
  51.  
  52. /* ############################################################################### */
  53. /* ##### DON'T MODIFY ANYTHING BELOW HERE UNLESS YOU KNOW WHAT YOU ARE DOING ##### */
  54. /* ############################################################################### */
  55.  
  56.  
  57. let selections,currentIndex
  58. let mapListener
  59. let isDrawing, isShift,isApplied,isASV,isYSV,isHidden
  60. let startX, startY, endX, endY
  61. let selectionBox
  62. let style
  63. let actionIndex=-1
  64.  
  65. function exportAsCsv(locs){
  66. const csvContent = jsonToCSV(locs);
  67. downloadCSV(csvContent);
  68. }
  69.  
  70. function downloadCSV(csvContent, fileName = "output.csv") {
  71. const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
  72. const link = document.createElement('a');
  73. if (link.download !== undefined) {
  74. const url = URL.createObjectURL(blob);
  75. link.setAttribute('href', url);
  76. link.setAttribute('download', fileName);
  77. link.style.visibility = 'hidden';
  78. document.body.appendChild(link);
  79. link.click();
  80. document.body.removeChild(link);
  81. }
  82. }
  83.  
  84. function getTagsForRow(item, maxTags) {
  85. const tags = item.tags || [];
  86. return Array.from({ length: maxTags }, (_, index) => tags[index] || '');
  87. }
  88.  
  89. function getFormattedDate(dateStr) {
  90. if (!dateStr) return '';
  91. const date = new Date(dateStr);
  92. if (isNaN(date.getTime())) return '';
  93. const year = date.getFullYear();
  94. const month = String(date.getMonth() + 1).padStart(2, '0');
  95. return `${year}-${month}`;
  96. }
  97.  
  98. function getMaxTagCount(jsonData) {
  99. let maxTags = 0;
  100. jsonData.forEach(item => {
  101. if (item.tags && item.tags.length > maxTags) {
  102. maxTags = item.tags.length;
  103. }
  104. });
  105. return maxTags;
  106. }
  107.  
  108. function jsonToCSV(jsonData) {
  109. const maxTags = getMaxTagCount(jsonData);
  110. const tagHeaders = Array.from({ length: maxTags }, (_, i) => `tag${i + 1}`);
  111. const headers = ["lat", "lng", "panoId", "heading", "pitch", "zoom", "date", ...tagHeaders];
  112. const rows = jsonData.map(item => {
  113. const lat = item.location.lat|| '';
  114. const lng = item.location.lng|| '';
  115. const panoId = item.panoId|| '';
  116. const heading = item.heading|| '';
  117. const pitch = item.pitch|| '';
  118. const zoom = item.zoom || '';
  119. const date = getFormattedDate(item.panoDate)||'';
  120. const tags = getTagsForRow(item, maxTags);
  121.  
  122. return [
  123. lat,
  124. lng,
  125. panoId,
  126. heading,
  127. pitch,
  128. zoom,
  129. date,
  130. ...tags
  131. ];
  132. });
  133.  
  134. const csvContent = [headers, ...rows].map(row => row.join(",")).join("\n");
  135. return csvContent;
  136. }
  137.  
  138. function switchLoc(locs) {
  139. if(editor.currentLocation) editor.closeLocation(editor.currentLocation.updatedProps)
  140. if (!currentIndex) {
  141. currentIndex =1;
  142. } else {
  143. currentIndex +=1
  144. if (currentIndex>locs.length){
  145. currentIndex=1
  146. }
  147. }
  148. editor.openLocation(locs[currentIndex-1]);
  149. focusOnLoc(locs[currentIndex-1])
  150. }
  151.  
  152. function rewindLoc(locs) {
  153. const activeSelections=editor.selections.length > 0 ? editor.selections.flatMap(selection => selection.locations) : locations
  154. if(editor.currentLocation) editor.closeLocation(editor.currentLocation.updatedProps)
  155. if (!currentIndex) {
  156. currentIndex =1;
  157. }
  158. else {
  159. currentIndex -=1
  160. if (currentIndex<1) currentIndex=activeSelections.length
  161. }
  162. editor.openLocation(locs[currentIndex-1]);
  163. focusOnLoc(locs[currentIndex-1])
  164. }
  165.  
  166. function focusOnLoc(loc){
  167. map.setCenter(loc.location)
  168. map.setZoom(17)
  169. }
  170.  
  171. function deleteLoc(loc){
  172. editor.closeAndDeleteLocation(loc)
  173. }
  174.  
  175. function copyLoc(){
  176. editor.addLocation(editor.currentLocation.updatedProps)
  177. }
  178.  
  179.  
  180. function setZoom(z){
  181. if(z<0)z=0
  182. if(z>4)z=4
  183. const svControl=unsafeWindow.streetView
  184. svControl.setZoom(z)
  185.  
  186. }
  187.  
  188. function getTag(index){
  189. const sortedTags = Object.keys(editor.tags).sort((a, b) => {
  190. return editor.tags[a].order - editor.tags[b].order;
  191. });
  192. const currentTags= editor.currentLocation.updatedProps.tags
  193. while(currentTags.includes(sortedTags[index-1])){
  194. index++
  195. }
  196. return sortedTags[index-1]
  197. }
  198.  
  199. async function tagLoc(index){
  200. if(editor.currentLocation){
  201. const tag=getTag(index)
  202. if(tag) await addTag(tag)
  203. }
  204. }
  205.  
  206.  
  207. async function addTag(tag){
  208. const isReview= document.querySelector('.review-header')
  209. const prevBtn = document.querySelector('[data-qa="review-prev"]');
  210. const nextBtn = document.querySelector('[data-qa="review-next"]');
  211. const currentLoc=editor.currentLocation.location
  212. const editLoc=editor.currentLocation.updatedProps
  213. if (isReview) {
  214. await editor.currentLocation.updatedProps.tags.push(tag)
  215. setTimeout(() => {
  216. if (nextBtn) nextBtn.click();
  217. }, 100);
  218. setTimeout(() => {
  219. if (prevBtn)prevBtn.click()
  220. }, 200);
  221. }
  222. else{
  223. await editor.closeAndDeleteLocation(editor.currentLocation.location)
  224. editLoc.tags.push(tag)
  225. await editor.addAndOpenLocation(editLoc)
  226. }
  227. }
  228.  
  229. function deleteTags() {
  230. let selections = editor.selections;
  231. while (selections.length > 0) {
  232. const item = selections[0];
  233. const tag = JSON.parse(item.key);
  234. const tagName = tag.tagName;
  235. const locations = item.locations;
  236.  
  237. editor.deleteTag(tagName, locations);
  238.  
  239. selections = editor.selections;
  240. }
  241. }
  242.  
  243. function customLayer(name,tileUrl,maxZoom,minZoom){
  244. return new google.maps.ImageMapType({
  245. getTileUrl: function(coord, zoom) {
  246. return tileUrl
  247. .replace('{z}', zoom)
  248. .replace('{x}', coord.x)
  249. .replace('{y}', coord.y);
  250. },
  251. tileSize: new google.maps.Size(256, 256),
  252. name: name,
  253. maxZoom:maxZoom,
  254. minZoom:minZoom||1
  255. });
  256. }
  257.  
  258. function classicMap(){
  259. var tileUrl = `https://mapsresources-pa.googleapis.com/v1/tiles?map_id=61449c20e7fc278b&version=15797339025669136861&pb=!1m7!8m6!1m3!1i{z}!2i{x}!3i{y}!2i9!3x1!2m2!1e0!2sm!3m7!2sen!3sus!5e1105!12m1!1e3!12m1!1e2!4e0!5m5!1e0!8m2!1e1!1e1!8i47083502!6m6!1e12!2i2!11e0!39b0!44e0!50e0`
  260. const tileLayer=customLayer('google_labels_reest',tileUrl,20)
  261. map.mapTypes.stack.layers[0]=tileLayer
  262. map.setMapTypeId('stack')
  263. }
  264.  
  265. function resetGulf(){
  266. var tileUrl = `https://maps.googleapis.com/maps/vt?pb=%211m5%211m4%211i{z}%212i{x}%213i{y}%214i256%212m2%211e0%212sm%213m17%212sen%213smx%215e18%2112m4%211e68%212m2%211sset%212sRoadmap%2112m3%211e37%212m1%211ssmartmaps%2112m4%211e26%212m2%211sstyles%212ss.e%3Ag%7Cp.v%3Aoff%2Cs.t%3A1%7Cs.e%3Ag.s%7Cp.v%3Aon%2Cs.e%3Al%7Cp.v%3Aon%215m1%215f1.350000023841858`
  267. if(JSON.parse(localStorage.getItem('mapBoldCountryBorders')))tileUrl=`https://maps.googleapis.com/maps/vt?pb=%211m5%211m4%211i{z}%212i{x}%213i{y}%214i256%212m2%211e0%212sm%213m17%212sen%213smx%215e18%2112m4%211e68%212m2%211sset%212sRoadmap%2112m3%211e37%212m1%211ssmartmaps%2112m4%211e26%212m2%211sstyles%212ss.t%3A17%7Cs.e%3Ag.s%7Cp.w%3A2%7Cp.c%3A%23000000%2Cs.e%3Ag%7Cp.v%3Aoff%2Cs.t%3A1%7Cs.e%3Ag.s%7Cp.v%3Aon%2Cs.e%3Al%7Cp.v%3Aon%215m1%215f1.350000023841858`
  268. if(JSON.parse(localStorage.getItem('mapBoldSubdivisionBorders')))tileUrl=`https://maps.googleapis.com/maps/vt?pb=%211m5%211m4%211i{z}%212i{x}%213i{y}%214i256%212m2%211e0%212sm%213m17%212sen%213smx%215e18%2112m4%211e68%212m2%211sset%212sRoadmap%2112m3%211e37%212m1%211ssmartmaps%2112m4%211e26%212m2%211sstyles%212ss.t%3A18%7Cs.e%3Ag.s%7Cp.w%3A3%2Cs.e%3Al%7Cp.v%3Aoff%2Cs.t%3A1%7Cs.e%3Ag.s%7Cp.v%3Aoff%215m1%215f1.350000023841858`
  269. if(JSON.parse(localStorage.getItem('mapBoldSubdivisionBorders'))&&JSON.parse(localStorage.getItem('mapBoldCountryBorders')))tileUrl=`https://maps.googleapis.com/maps/vt?pb=%211m5%211m4%211i{z}%212i{x}%213i{y}%214i256%212m2%211e0%212sm%213m17%212sen%213smx%215e18%2112m4%211e68%212m2%211sset%212sRoadmap%2112m3%211e37%212m1%211ssmartmaps%2112m4%211e26%212m2%211sstyles%212ss.t%3A17%7Cs.e%3Ag.s%7Cp.w%3A2%7Cp.c%3A%23000000%2Cs.t%3A18%7Cs.e%3Ag.s%7Cp.w%3A3%2Cs.e%3Ag%7Cp.v%3Aoff%2Cs.t%3A1%7Cs.e%3Ag.s%7Cp.v%3Aon%2Cs.e%3Al%7Cp.v%3Aon%215m1%215f1.350000023841858`
  270. const tileLayer=customLayer('google_labels_reest',tileUrl,20)
  271. map.mapTypes.stack.layers[2]=tileLayer
  272. map.setMapTypeId('stack')
  273. }
  274.  
  275. function setHW(){
  276. map.mapTypes.stack.layers.splice(2, 1)
  277. const tileUrl = `https://maprastertile-drcn.dbankcdn.cn/display-service/v1/online-render/getTile/24.12.10.10/{z}/{x}/{y}/?language=zh&p=46&scale=2&mapType=ROADMAP&presetStyleId=standard&pattern=JPG&key=DAEDANitav6P7Q0lWzCzKkLErbrJG4kS1u%2FCpEe5ZyxW5u0nSkb40bJ%2BYAugRN03fhf0BszLS1rCrzAogRHDZkxaMrloaHPQGO6LNg==`
  278. const tileLayer=customLayer('Petal_Maps',tileUrl,20)
  279. map.mapTypes.stack.layers[0]=tileLayer
  280. map.setMapTypeId('stack')
  281. }
  282.  
  283. function setGD(){
  284. const tileUrl = `https://t2.tianditu.gov.cn/ter_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=75f0434f240669f4a2df6359275146d2`
  285. const tileLayer=customLayer('GaoDe_Terrain',tileUrl,20)
  286. //map.mapTypes.stack.layers[0]=tileLayer
  287.  
  288. const tileUrl_ = `https://t2.tianditu.gov.cn/tbo_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=tbo&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=75f0434f240669f4a2df6359275146d2`
  289. const tileLayer_=customLayer('GaoDe_Border',tileUrl_,20)
  290. map.mapTypes.stack.layers[1]=tileLayer_
  291.  
  292. const _tileUrl = `https://t2.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=75f0434f240669f4a2df6359275146d2`
  293. const _tileLayer=customLayer('GaoDe_Labels',_tileUrl,20)
  294. //map.mapTypes.stack.layers[2]=_tileLayer
  295.  
  296. map.setMapTypeId('stack')
  297. }
  298.  
  299. function setYandex(){
  300. const svUrl=`https://core-stv-renderer.maps.yandex.net/2.x/tiles?l=stv&x={x}&y={y}&z={z}&scale=1&v=2025.04.04.20.13-1_25.03.31-4-24330`
  301. const baseUrl=`https://core-renderer-tiles.maps.yandex.net/tiles?l=map&v=5.04.07-2~b:250311142430~ib:250404100358-24371&x={x}&y={y}&z={z}&scale=1&lang=en_US`
  302. const svLayer=customLayer('Yandex_StreetView',svUrl,20,5)
  303. const baseLayer=customLayer('Yandex_Maps',baseUrl,20,1)
  304. map.mapTypes.stack.layers.splice(2, 0,svLayer)
  305. map.mapTypes.stack.layers.splice(2, 0,baseLayer)
  306. map.mapTypes.set("stack",map.mapTypes.stack.layers)
  307. map.setMapTypeId('stack')
  308. }
  309.  
  310. function setApple(){
  311. const svUrl=`https://lookmap.eu.pythonanywhere.com/bluelines_raster_2x/{z}/{x}/{y}.png`
  312. const svLayer=customLayer('Apple_StreetView',svUrl,16)
  313. map.mapTypes.stack.layers.splice(2, 0,svLayer)
  314. map.setMapTypeId('stack')
  315.  
  316. }
  317.  
  318. function getBingTilesUrl(tileX, tileY, zoom, type) {
  319. var quadKey = tileXYToQuadKey(tileX, tileY, zoom);
  320. const tileUrl=`https://t.ssl.ak.dynamic.tiles.virtualearth.net/comp/ch/${quadKey}?it=Z,HC`
  321. return tileUrl
  322. }
  323.  
  324. function tileXYToQuadKey(tileX, tileY, zoom) {
  325. var quadKey = '';
  326. for (var i = zoom; i > 0; i--) {
  327. var digit = 0;
  328. var mask = 1 << (i - 1);
  329. if ((tileX & mask) !== 0) {
  330. digit += 1;
  331. }
  332. if ((tileY & mask) !== 0) {
  333. digit += 2;
  334. }
  335. quadKey += digit.toString();
  336. }
  337. return quadKey;
  338. }
  339.  
  340. function setBing(){
  341. const svLayer = new google.maps.ImageMapType({
  342. getTileUrl: function(coord, zoom) {
  343. return getBingTilesUrl(coord.x,coord.y,zoom)
  344. .replace('{z}', zoom)
  345. .replace('{x}', coord.x)
  346. .replace('{y}', coord.y);
  347. },
  348. tileSize: new google.maps.Size(256, 256),
  349. name: 'Bing_StreetSide',
  350. maxZoom:20
  351. });
  352. map.mapTypes.stack.layers.splice(2, 0,svLayer)
  353. map.setMapTypeId('stack')
  354. }
  355.  
  356. async function downloadTile(id,g) {
  357. try {
  358. const response = await fetch(`https://streetviewpixels-pa.googleapis.com/v1/tile?cb_client=apiv3&panoid=${id}&output=tile&x=${g==='Gen4'?18:16}&y=${g==='Gen4'?13:11}&zoom=5&nbt=1&fover=2`);
  359. const imageBlob = await response.blob();
  360. const img = new Image();
  361. img.onload = function() {
  362. const canvas = document.createElement('canvas');
  363. const ctx = canvas.getContext('2d');
  364. canvas.width = img.width;
  365. canvas.height = img.height;
  366. ctx.drawImage(img, 0, 0);
  367.  
  368. const dataUrl = canvas.toDataURL('image/jpeg');
  369. const link = document.createElement('a');
  370. link.href = dataUrl;
  371. link.download = id+'.jpg';
  372. link.click();
  373. };
  374. img.src = URL.createObjectURL(imageBlob);
  375. } catch (error) {
  376. console.error('Error:', error);
  377. }
  378. }
  379.  
  380. function lon2tile(lng,zoom) {
  381. return (lng+180)/360*Math.pow(2,zoom);
  382. }
  383.  
  384. function lat2tile(lat,zoom){
  385. return (1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,zoom);
  386. }
  387.  
  388. function lonLatToEpsg3395Tile(lng, lat, zoom) {
  389. return [lon2tile(lng,zoom), lat2tile(lat,zoom)];
  390. }
  391.  
  392. function getMap(){
  393. let element = document.getElementsByClassName("map-embed")[0]
  394. try{
  395. const keys = Object.keys(element)
  396. const key = keys.find(key => key.startsWith("__reactFiber$"))
  397. const props = element[key]
  398. if(!map)window.map=props.pendingProps.children[1].props.children[1].props.map
  399. }
  400. catch(e){
  401. console.error('Failed to get map: '+e)
  402. }
  403. }
  404.  
  405. function delay(ms) {
  406. return new Promise(resolve => setTimeout(resolve, ms));
  407. }
  408.  
  409.  
  410. async function fetchPanorama(service,panoId) {
  411.  
  412. await delay(100);
  413. return await service.getPanorama({ pano: panoId });
  414. }
  415.  
  416. async function findLinkPanos() {
  417. const startLoc = editor.currentLocation.updatedProps;
  418. let prevHeading = startLoc.heading;
  419. let service = new google.maps.StreetViewService();
  420. let metadata = await fetchPanorama(service,startLoc.panoId);
  421. while (metadata.data.links.length == 2) {
  422. let nextLoc = metadata.data.links.find(loc => Math.abs(loc.heading - prevHeading) <= 90);
  423. if (nextLoc) {
  424. metadata = await fetchPanorama(service,nextLoc.pano);
  425. editor.addLocation({
  426. location: {
  427. lat: metadata.data.location.latLng.lat(),
  428. lng: metadata.data.location.latLng.lng()
  429. },
  430. panoId: metadata.data.location.pano,
  431. heading: nextLoc.heading,
  432. pitch: 0,
  433. zoom: 0,
  434. tags: [],
  435. flags: 1
  436. });
  437. prevHeading = nextLoc.heading;
  438. }
  439. else break
  440. }
  441. }
  442.  
  443. function toggleElementHidden() {
  444. if(!isHidden){
  445. style = GM_addStyle(`
  446. .embed-controls {display: none !important}
  447. .SLHIdE-sv-links-control {display: none !important}
  448. [class$="gmnoprint"], [class$="gm-style-cc"] {display: none !important}
  449. `);
  450. isHidden = true;
  451. }
  452. else{
  453. style.remove()
  454. isHidden=false;
  455. }
  456. }
  457.  
  458.  
  459. let onKeyDown =async (e) => {
  460. if (e.target.tagName === 'INPUT' || e.target.isContentEditable||!isApplied) {
  461. return;
  462. }
  463.  
  464. const activeSelections=editor.selections.length > 0 ? editor.selections.flatMap(selection => selection.locations) : locations
  465.  
  466. if (e.key >= '1' && e.key <= '9'){
  467. e.stopImmediatePropagation();
  468. const tagIndex=parseInt(e.key)
  469. await tagLoc(tagIndex)
  470. }
  471.  
  472. if (!e.shiftKey&&!e.ctrlKey&&(e.key === KEYBOARD_SHORTCUTS.hideElement || e.key === KEYBOARD_SHORTCUTS.hideElement.toLowerCase())) {
  473. e.stopImmediatePropagation();
  474. toggleElementHidden()
  475. }
  476.  
  477. else if (!e.shiftKey&&!e.ctrlKey&&(e.key === KEYBOARD_SHORTCUTS.deSelectAll || e.key === KEYBOARD_SHORTCUTS.deSelectAll.toLowerCase())) {
  478. e.stopImmediatePropagation();
  479. editor.resetSelections()
  480. }
  481.  
  482. else if (!e.shiftKey&&!e.ctrlKey&&(e.key === KEYBOARD_SHORTCUTS.copyLoc || e.key === KEYBOARD_SHORTCUTS.copyLoc.toLowerCase())) {
  483. e.stopImmediatePropagation();
  484. copyLoc()
  485. }
  486.  
  487. else if (e.key === 'I' || e.key === 'i') {
  488. e.stopImmediatePropagation();
  489. setBing()
  490. }
  491.  
  492. else if (e.key === KEYBOARD_SHORTCUTS.switchLoc || e.key === KEYBOARD_SHORTCUTS.switchLoc.toLowerCase()) {
  493. e.stopImmediatePropagation();
  494. switchLoc(activeSelections)
  495. }
  496.  
  497. else if (e.key === KEYBOARD_SHORTCUTS.rewindLoc || e.key === KEYBOARD_SHORTCUTS.rewindLoc.toLowerCase()) {
  498. e.stopImmediatePropagation();
  499. rewindLoc(activeSelections)
  500. }
  501.  
  502. else if (!e.shiftKey&&!e.ctrlKey&&(e.key === KEYBOARD_SHORTCUTS.deleteLoc || e.key === KEYBOARD_SHORTCUTS.deleteLoc.toLowerCase())) {
  503. e.stopImmediatePropagation();
  504. deleteLoc(activeSelections[currentIndex-1])
  505. }
  506. else if (!e.shiftKey&&!e.ctrlKey&&(e.key === KEYBOARD_SHORTCUTS.closeLoc || e.key === KEYBOARD_SHORTCUTS.closeLoc.toLowerCase())) {
  507. e.stopImmediatePropagation();
  508. editor.closeLocation(editor.currentLocation.updatedProps)
  509. }
  510.  
  511. else if (e.key === KEYBOARD_SHORTCUTS.pruneDuplicates || e.key === KEYBOARD_SHORTCUTS.pruneDuplicates.toLowerCase()){
  512. const duplicates=editor.selections.flatMap(selection=>{if (selection.key.includes('dup') ) return selection})
  513. if (duplicates.length>0){
  514. duplicates.forEach((dup)=>{
  515. editor.pruneDuplicates(dup)
  516. })
  517. }
  518. }
  519.  
  520. if ((e.shiftKey)&&(e.key === KEYBOARD_SHORTCUTS.exportAsCsv || e.key === KEYBOARD_SHORTCUTS.exportAsCsv.toLowerCase())) {
  521. e.stopImmediatePropagation();
  522. exportAsCsv(activeSelections)
  523. }
  524.  
  525. if ((e.shiftKey)&&(e.key === KEYBOARD_SHORTCUTS.resetGulf || e.key === KEYBOARD_SHORTCUTS.resetGulf.toLowerCase())) {
  526. e.stopImmediatePropagation();
  527. resetGulf()
  528. }
  529.  
  530. if ((e.shiftKey)&&(e.key === KEYBOARD_SHORTCUTS.classicMap || e.key === KEYBOARD_SHORTCUTS.classicMap.toLowerCase())) {
  531. e.stopImmediatePropagation();
  532. classicMap()
  533. }
  534.  
  535. if ((e.shiftKey)&&(e.key === KEYBOARD_SHORTCUTS.deleteTags || e.key === KEYBOARD_SHORTCUTS.deleteTags.toLowerCase())) {
  536. e.stopImmediatePropagation();
  537. deleteTags()
  538. }
  539.  
  540. if (e.shiftKey&&(e.key === KEYBOARD_SHORTCUTS.findLinkPanos || e.key === KEYBOARD_SHORTCUTS.findLinkPanos.toLowerCase())) {
  541. e.stopImmediatePropagation();
  542. findLinkPanos();
  543. }
  544. if ((e.shiftKey)&&(e.key === KEYBOARD_SHORTCUTS.fullScreenMap || e.key === KEYBOARD_SHORTCUTS.fullScreenMap.toLowerCase())) {
  545. e.stopImmediatePropagation();
  546. document.getElementsByClassName("gm-fullscreen-control")[0].click()
  547. }
  548. if ((e.shiftKey)&&(e.key === 'h' || e.key === 'H')) {
  549. e.stopImmediatePropagation();
  550. setHW()
  551. }
  552. /*if (e.key === 't' || e.key === 'T') {
  553. e.stopImmediatePropagation();
  554. getEditor()
  555. const panos=[]
  556. for (const loc of selections) {
  557. const panoId=loc.panoId
  558. var gen
  559. if (loc.tags.includes('Gen4')) gen='Gen4'
  560. if(panoId) panos.push({id:panoId,g:gen})
  561. }
  562. const downloadPromises = panos.map(pano => downloadTile(pano.id, pano.g));
  563. await Promise.all(downloadPromises);
  564.  
  565. };*/
  566. }
  567. document.addEventListener("keydown", onKeyDown);
  568.  
  569. var shortCutButton = document.createElement('button');
  570. shortCutButton.textContent = 'Shortcut Off';
  571. shortCutButton.style.position = 'absolute';
  572. shortCutButton.style.top = '8px';
  573. shortCutButton.style.right = '700px';
  574. shortCutButton.style.zIndex = '9999';
  575. shortCutButton.style.borderRadius = "18px";
  576. shortCutButton.style.padding = "5px 10px";
  577. shortCutButton.style.border = "none";
  578. shortCutButton.style.backgroundColor = "#4CAF50";
  579. shortCutButton.style.color = "white";
  580. shortCutButton.style.cursor = "pointer";
  581. shortCutButton.addEventListener('click', function(){
  582. if(isApplied){
  583. isApplied=false
  584. shortCutButton.style.border='none'
  585. shortCutButton.textContent = 'Shortcut Off';
  586. }
  587. else {isApplied=true
  588. shortCutButton.textContent = 'ShortCut On';
  589. shortCutButton.style.border='2px solid #fff'}
  590.  
  591. });
  592. document.body.appendChild(shortCutButton)
  593.  
  594. document.addEventListener('keydown', function(e) {
  595. if (e.shiftKey ) {
  596. isShift = true;
  597. }
  598. });
  599. document.addEventListener('keyup', function(e) {
  600. if (!e.shiftKey ) {
  601. isShift = false;
  602. }
  603. });
  604.  
  605. document.addEventListener('mousedown', function(e) {
  606. if (e.button === 0&&isShift) {
  607. isDrawing = true;
  608. startX = e.clientX;
  609. startY = e.clientY;
  610. document.body.style.userSelect = 'none'
  611. selectionBox = document.createElement('div');
  612. selectionBox.style.position = 'absolute';
  613. selectionBox.style.border = '2px solid rgba(0, 128, 255, 0.7)';
  614. selectionBox.style.backgroundColor = 'rgba(0, 128, 255, 0.2)';
  615. document.body.appendChild(selectionBox);
  616. }
  617. });
  618.  
  619. document.addEventListener('mousemove', function(e) {
  620. if (isDrawing) {
  621. endX = e.clientX;
  622. endY = e.clientY;
  623.  
  624. const width = Math.abs(endX - startX);
  625. const height = Math.abs(endY - startY);
  626. selectionBox.style.left = `${Math.min(startX, endX)}px`;
  627. selectionBox.style.top = `${Math.min(startY, endY)}px`;
  628. selectionBox.style.width = `${width}px`;
  629. selectionBox.style.height = `${height}px`;
  630. selectionBox.style.zIndex = '999999';
  631. }
  632. });
  633.  
  634. document.addEventListener('mouseup', function(e) {
  635. if (isDrawing) {
  636. isDrawing = false;
  637. const rect = selectionBox.getBoundingClientRect();
  638. document.body.removeChild(selectionBox);
  639. const elements = document.querySelectorAll('ul.tag-list');
  640. elements.forEach(element => {
  641. const childrens = element.querySelectorAll('li.tag.has-button');
  642. childrens.forEach(child => {
  643. const childRect = child.getBoundingClientRect();
  644. if (
  645. childRect.top >= rect.top &&
  646. childRect.left >= rect.left &&
  647. childRect.bottom <= rect.bottom &&
  648. childRect.right <= rect.right
  649. ) {
  650. child.click();
  651. document.body.style.userSelect = 'text';
  652. }
  653. });
  654. });
  655. }
  656. });
  657. })();