Greasy Fork 还支持 简体中文。

WME WazeMY

WazeMY beta

  1. // ==UserScript==
  2. // @name WME WazeMY
  3. // @namespace https://www.github.com/junyian/
  4. // @version 2025.08.29.02
  5. // @author junyianl <junyian@gmail.com>
  6. // @source https://github.com/junyian/wme-wazemy
  7. // @license MIT
  8. // @match *://www.waze.com/editor*
  9. // @match *://www.waze.com/*/editor*
  10. // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
  11. // @require https://greasyfork.org/scripts/449165-wme-wazemy-trafcamlist/code/wme-wazemy-trafcamlist.js
  12. // @grant GM_xmlhttpRequest
  13. // @grant GM.xmlHttpRequest
  14. // @grant unsafeWindow
  15. // @connect p3.fgies.com
  16. // @connect p4.fgies.com
  17. // @connect t2.fgies.com
  18. // @connect jalanow.com
  19. // @connect llm.gov.my
  20. // @connect venue-image.waze.com
  21. // @connect generativelanguage.googleapis.com
  22. // @run-at document-end
  23. // @description WazeMY beta
  24. // ==/UserScript==
  25.  
  26. /******/ (() => { // webpackBootstrap
  27. /******/ "use strict";
  28. /******/ var __webpack_modules__ = ({
  29.  
  30. /***/ "./node_modules/css-loader/dist/cjs.js!./node_modules/less-loader/dist/cjs.js!./src/style/main.less":
  31. /***/ ((module, __webpack_exports__, __webpack_require__) => {
  32.  
  33. /* harmony export */ __webpack_require__.d(__webpack_exports__, {
  34. /* harmony export */ A: () => (__WEBPACK_DEFAULT_EXPORT__)
  35. /* harmony export */ });
  36. /* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./node_modules/css-loader/dist/runtime/noSourceMaps.js");
  37. /* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
  38. /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("./node_modules/css-loader/dist/runtime/api.js");
  39. /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
  40. // Imports
  41.  
  42.  
  43. var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));
  44. // Module
  45. ___CSS_LOADER_EXPORT___.push([module.id, `.wazemySettings {
  46. border: 1px solid;
  47. padding: 8px;
  48. border-radius: 4px;
  49. }
  50. .wazemySettings legend {
  51. margin-bottom: 0px;
  52. border-bottom-style: none;
  53. width: auto;
  54. }
  55. .wazemySettings h6 {
  56. margin-bottom: 0px;
  57. }
  58. .wazemySettings input {
  59. margin-top: 0px;
  60. }
  61. #wazemyTooltip {
  62. height: auto;
  63. width: auto;
  64. background-color: rgba(0, 0, 0, 0.5);
  65. color: white;
  66. border-radius: 4px;
  67. padding: 4px;
  68. position: absolute;
  69. top: 0px;
  70. left: 0px;
  71. visibility: hidden;
  72. z-index: 10000;
  73. }
  74. #wazemyPlaces_table {
  75. overflow-x: scroll;
  76. }
  77. #wazemyPlaces_venues {
  78. width: 95%;
  79. border: 1px solid;
  80. }
  81. #wazemyPlaces_venues th {
  82. border: 1px solid;
  83. background-color: #ccc;
  84. }
  85. #wazemyPlaces_venues td {
  86. border: 1px solid;
  87. }
  88. `, ""]);
  89. // Exports
  90. /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
  91.  
  92.  
  93. /***/ }),
  94.  
  95. /***/ "./node_modules/css-loader/dist/runtime/api.js":
  96. /***/ ((module) => {
  97.  
  98.  
  99.  
  100. /*
  101. MIT License http://www.opensource.org/licenses/mit-license.php
  102. Author Tobias Koppers @sokra
  103. */
  104. module.exports = function (cssWithMappingToString) {
  105. var list = [];
  106.  
  107. // return the list of modules as css string
  108. list.toString = function toString() {
  109. return this.map(function (item) {
  110. var content = "";
  111. var needLayer = typeof item[5] !== "undefined";
  112. if (item[4]) {
  113. content += "@supports (".concat(item[4], ") {");
  114. }
  115. if (item[2]) {
  116. content += "@media ".concat(item[2], " {");
  117. }
  118. if (needLayer) {
  119. content += "@layer".concat(item[5].length > 0 ? " ".concat(item[5]) : "", " {");
  120. }
  121. content += cssWithMappingToString(item);
  122. if (needLayer) {
  123. content += "}";
  124. }
  125. if (item[2]) {
  126. content += "}";
  127. }
  128. if (item[4]) {
  129. content += "}";
  130. }
  131. return content;
  132. }).join("");
  133. };
  134.  
  135. // import a list of modules into the list
  136. list.i = function i(modules, media, dedupe, supports, layer) {
  137. if (typeof modules === "string") {
  138. modules = [[null, modules, undefined]];
  139. }
  140. var alreadyImportedModules = {};
  141. if (dedupe) {
  142. for (var k = 0; k < this.length; k++) {
  143. var id = this[k][0];
  144. if (id != null) {
  145. alreadyImportedModules[id] = true;
  146. }
  147. }
  148. }
  149. for (var _k = 0; _k < modules.length; _k++) {
  150. var item = [].concat(modules[_k]);
  151. if (dedupe && alreadyImportedModules[item[0]]) {
  152. continue;
  153. }
  154. if (typeof layer !== "undefined") {
  155. if (typeof item[5] === "undefined") {
  156. item[5] = layer;
  157. } else {
  158. item[1] = "@layer".concat(item[5].length > 0 ? " ".concat(item[5]) : "", " {").concat(item[1], "}");
  159. item[5] = layer;
  160. }
  161. }
  162. if (media) {
  163. if (!item[2]) {
  164. item[2] = media;
  165. } else {
  166. item[1] = "@media ".concat(item[2], " {").concat(item[1], "}");
  167. item[2] = media;
  168. }
  169. }
  170. if (supports) {
  171. if (!item[4]) {
  172. item[4] = "".concat(supports);
  173. } else {
  174. item[1] = "@supports (".concat(item[4], ") {").concat(item[1], "}");
  175. item[4] = supports;
  176. }
  177. }
  178. list.push(item);
  179. }
  180. };
  181. return list;
  182. };
  183.  
  184. /***/ }),
  185.  
  186. /***/ "./node_modules/css-loader/dist/runtime/noSourceMaps.js":
  187. /***/ ((module) => {
  188.  
  189.  
  190.  
  191. module.exports = function (i) {
  192. return i[1];
  193. };
  194.  
  195. /***/ }),
  196.  
  197. /***/ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js":
  198. /***/ ((module) => {
  199.  
  200.  
  201.  
  202. var stylesInDOM = [];
  203. function getIndexByIdentifier(identifier) {
  204. var result = -1;
  205. for (var i = 0; i < stylesInDOM.length; i++) {
  206. if (stylesInDOM[i].identifier === identifier) {
  207. result = i;
  208. break;
  209. }
  210. }
  211. return result;
  212. }
  213. function modulesToDom(list, options) {
  214. var idCountMap = {};
  215. var identifiers = [];
  216. for (var i = 0; i < list.length; i++) {
  217. var item = list[i];
  218. var id = options.base ? item[0] + options.base : item[0];
  219. var count = idCountMap[id] || 0;
  220. var identifier = "".concat(id, " ").concat(count);
  221. idCountMap[id] = count + 1;
  222. var indexByIdentifier = getIndexByIdentifier(identifier);
  223. var obj = {
  224. css: item[1],
  225. media: item[2],
  226. sourceMap: item[3],
  227. supports: item[4],
  228. layer: item[5]
  229. };
  230. if (indexByIdentifier !== -1) {
  231. stylesInDOM[indexByIdentifier].references++;
  232. stylesInDOM[indexByIdentifier].updater(obj);
  233. } else {
  234. var updater = addElementStyle(obj, options);
  235. options.byIndex = i;
  236. stylesInDOM.splice(i, 0, {
  237. identifier: identifier,
  238. updater: updater,
  239. references: 1
  240. });
  241. }
  242. identifiers.push(identifier);
  243. }
  244. return identifiers;
  245. }
  246. function addElementStyle(obj, options) {
  247. var api = options.domAPI(options);
  248. api.update(obj);
  249. var updater = function updater(newObj) {
  250. if (newObj) {
  251. if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap && newObj.supports === obj.supports && newObj.layer === obj.layer) {
  252. return;
  253. }
  254. api.update(obj = newObj);
  255. } else {
  256. api.remove();
  257. }
  258. };
  259. return updater;
  260. }
  261. module.exports = function (list, options) {
  262. options = options || {};
  263. list = list || [];
  264. var lastIdentifiers = modulesToDom(list, options);
  265. return function update(newList) {
  266. newList = newList || [];
  267. for (var i = 0; i < lastIdentifiers.length; i++) {
  268. var identifier = lastIdentifiers[i];
  269. var index = getIndexByIdentifier(identifier);
  270. stylesInDOM[index].references--;
  271. }
  272. var newLastIdentifiers = modulesToDom(newList, options);
  273. for (var _i = 0; _i < lastIdentifiers.length; _i++) {
  274. var _identifier = lastIdentifiers[_i];
  275. var _index = getIndexByIdentifier(_identifier);
  276. if (stylesInDOM[_index].references === 0) {
  277. stylesInDOM[_index].updater();
  278. stylesInDOM.splice(_index, 1);
  279. }
  280. }
  281. lastIdentifiers = newLastIdentifiers;
  282. };
  283. };
  284.  
  285. /***/ }),
  286.  
  287. /***/ "./node_modules/style-loader/dist/runtime/insertBySelector.js":
  288. /***/ ((module) => {
  289.  
  290.  
  291.  
  292. var memo = {};
  293.  
  294. /* istanbul ignore next */
  295. function getTarget(target) {
  296. if (typeof memo[target] === "undefined") {
  297. var styleTarget = document.querySelector(target);
  298.  
  299. // Special case to return head of iframe instead of iframe itself
  300. if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) {
  301. try {
  302. // This will throw an exception if access to iframe is blocked
  303. // due to cross-origin restrictions
  304. styleTarget = styleTarget.contentDocument.head;
  305. } catch (e) {
  306. // istanbul ignore next
  307. styleTarget = null;
  308. }
  309. }
  310. memo[target] = styleTarget;
  311. }
  312. return memo[target];
  313. }
  314.  
  315. /* istanbul ignore next */
  316. function insertBySelector(insert, style) {
  317. var target = getTarget(insert);
  318. if (!target) {
  319. throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");
  320. }
  321. target.appendChild(style);
  322. }
  323. module.exports = insertBySelector;
  324.  
  325. /***/ }),
  326.  
  327. /***/ "./node_modules/style-loader/dist/runtime/insertStyleElement.js":
  328. /***/ ((module) => {
  329.  
  330.  
  331.  
  332. /* istanbul ignore next */
  333. function insertStyleElement(options) {
  334. var element = document.createElement("style");
  335. options.setAttributes(element, options.attributes);
  336. options.insert(element, options.options);
  337. return element;
  338. }
  339. module.exports = insertStyleElement;
  340.  
  341. /***/ }),
  342.  
  343. /***/ "./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js":
  344. /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
  345.  
  346.  
  347.  
  348. /* istanbul ignore next */
  349. function setAttributesWithoutAttributes(styleElement) {
  350. var nonce = true ? __webpack_require__.nc : 0;
  351. if (nonce) {
  352. styleElement.setAttribute("nonce", nonce);
  353. }
  354. }
  355. module.exports = setAttributesWithoutAttributes;
  356.  
  357. /***/ }),
  358.  
  359. /***/ "./node_modules/style-loader/dist/runtime/styleDomAPI.js":
  360. /***/ ((module) => {
  361.  
  362.  
  363.  
  364. /* istanbul ignore next */
  365. function apply(styleElement, options, obj) {
  366. var css = "";
  367. if (obj.supports) {
  368. css += "@supports (".concat(obj.supports, ") {");
  369. }
  370. if (obj.media) {
  371. css += "@media ".concat(obj.media, " {");
  372. }
  373. var needLayer = typeof obj.layer !== "undefined";
  374. if (needLayer) {
  375. css += "@layer".concat(obj.layer.length > 0 ? " ".concat(obj.layer) : "", " {");
  376. }
  377. css += obj.css;
  378. if (needLayer) {
  379. css += "}";
  380. }
  381. if (obj.media) {
  382. css += "}";
  383. }
  384. if (obj.supports) {
  385. css += "}";
  386. }
  387. var sourceMap = obj.sourceMap;
  388. if (sourceMap && typeof btoa !== "undefined") {
  389. css += "\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), " */");
  390. }
  391.  
  392. // For old IE
  393. /* istanbul ignore if */
  394. options.styleTagTransform(css, styleElement, options.options);
  395. }
  396. function removeStyleElement(styleElement) {
  397. // istanbul ignore if
  398. if (styleElement.parentNode === null) {
  399. return false;
  400. }
  401. styleElement.parentNode.removeChild(styleElement);
  402. }
  403.  
  404. /* istanbul ignore next */
  405. function domAPI(options) {
  406. if (typeof document === "undefined") {
  407. return {
  408. update: function update() {},
  409. remove: function remove() {}
  410. };
  411. }
  412. var styleElement = options.insertStyleElement(options);
  413. return {
  414. update: function update(obj) {
  415. apply(styleElement, options, obj);
  416. },
  417. remove: function remove() {
  418. removeStyleElement(styleElement);
  419. }
  420. };
  421. }
  422. module.exports = domAPI;
  423.  
  424. /***/ }),
  425.  
  426. /***/ "./node_modules/style-loader/dist/runtime/styleTagTransform.js":
  427. /***/ ((module) => {
  428.  
  429.  
  430.  
  431. /* istanbul ignore next */
  432. function styleTagTransform(css, styleElement) {
  433. if (styleElement.styleSheet) {
  434. styleElement.styleSheet.cssText = css;
  435. } else {
  436. while (styleElement.firstChild) {
  437. styleElement.removeChild(styleElement.firstChild);
  438. }
  439. styleElement.appendChild(document.createTextNode(css));
  440. }
  441. }
  442. module.exports = styleTagTransform;
  443.  
  444. /***/ })
  445.  
  446. /******/ });
  447. /************************************************************************/
  448. /******/ // The module cache
  449. /******/ var __webpack_module_cache__ = {};
  450. /******/
  451. /******/ // The require function
  452. /******/ function __webpack_require__(moduleId) {
  453. /******/ // Check if module is in cache
  454. /******/ var cachedModule = __webpack_module_cache__[moduleId];
  455. /******/ if (cachedModule !== undefined) {
  456. /******/ return cachedModule.exports;
  457. /******/ }
  458. /******/ // Create a new module (and put it into the cache)
  459. /******/ var module = __webpack_module_cache__[moduleId] = {
  460. /******/ id: moduleId,
  461. /******/ // no module.loaded needed
  462. /******/ exports: {}
  463. /******/ };
  464. /******/
  465. /******/ // Execute the module function
  466. /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  467. /******/
  468. /******/ // Return the exports of the module
  469. /******/ return module.exports;
  470. /******/ }
  471. /******/
  472. /************************************************************************/
  473. /******/ /* webpack/runtime/compat get default export */
  474. /******/ (() => {
  475. /******/ // getDefaultExport function for compatibility with non-harmony modules
  476. /******/ __webpack_require__.n = (module) => {
  477. /******/ var getter = module && module.__esModule ?
  478. /******/ () => (module['default']) :
  479. /******/ () => (module);
  480. /******/ __webpack_require__.d(getter, { a: getter });
  481. /******/ return getter;
  482. /******/ };
  483. /******/ })();
  484. /******/
  485. /******/ /* webpack/runtime/define property getters */
  486. /******/ (() => {
  487. /******/ // define getter functions for harmony exports
  488. /******/ __webpack_require__.d = (exports, definition) => {
  489. /******/ for(var key in definition) {
  490. /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
  491. /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
  492. /******/ }
  493. /******/ }
  494. /******/ };
  495. /******/ })();
  496. /******/
  497. /******/ /* webpack/runtime/hasOwnProperty shorthand */
  498. /******/ (() => {
  499. /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
  500. /******/ })();
  501. /******/
  502. /******/ /* webpack/runtime/nonce */
  503. /******/ (() => {
  504. /******/ __webpack_require__.nc = undefined;
  505. /******/ })();
  506. /******/
  507. /************************************************************************/
  508. var __webpack_exports__ = {};
  509.  
  510. // EXTERNAL MODULE: ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js
  511. var injectStylesIntoStyleTag = __webpack_require__("./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
  512. var injectStylesIntoStyleTag_default = /*#__PURE__*/__webpack_require__.n(injectStylesIntoStyleTag);
  513. // EXTERNAL MODULE: ./node_modules/style-loader/dist/runtime/styleDomAPI.js
  514. var styleDomAPI = __webpack_require__("./node_modules/style-loader/dist/runtime/styleDomAPI.js");
  515. var styleDomAPI_default = /*#__PURE__*/__webpack_require__.n(styleDomAPI);
  516. // EXTERNAL MODULE: ./node_modules/style-loader/dist/runtime/insertBySelector.js
  517. var insertBySelector = __webpack_require__("./node_modules/style-loader/dist/runtime/insertBySelector.js");
  518. var insertBySelector_default = /*#__PURE__*/__webpack_require__.n(insertBySelector);
  519. // EXTERNAL MODULE: ./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js
  520. var setAttributesWithoutAttributes = __webpack_require__("./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js");
  521. var setAttributesWithoutAttributes_default = /*#__PURE__*/__webpack_require__.n(setAttributesWithoutAttributes);
  522. // EXTERNAL MODULE: ./node_modules/style-loader/dist/runtime/insertStyleElement.js
  523. var insertStyleElement = __webpack_require__("./node_modules/style-loader/dist/runtime/insertStyleElement.js");
  524. var insertStyleElement_default = /*#__PURE__*/__webpack_require__.n(insertStyleElement);
  525. // EXTERNAL MODULE: ./node_modules/style-loader/dist/runtime/styleTagTransform.js
  526. var styleTagTransform = __webpack_require__("./node_modules/style-loader/dist/runtime/styleTagTransform.js");
  527. var styleTagTransform_default = /*#__PURE__*/__webpack_require__.n(styleTagTransform);
  528. // EXTERNAL MODULE: ./node_modules/css-loader/dist/cjs.js!./node_modules/less-loader/dist/cjs.js!./src/style/main.less
  529. var main = __webpack_require__("./node_modules/css-loader/dist/cjs.js!./node_modules/less-loader/dist/cjs.js!./src/style/main.less");
  530. ;// ./src/style/main.less
  531.  
  532.  
  533. var options = {};
  534.  
  535. options.styleTagTransform = (styleTagTransform_default());
  536. options.setAttributes = (setAttributesWithoutAttributes_default());
  537. options.insert = insertBySelector_default().bind(null, "head");
  538. options.domAPI = (styleDomAPI_default());
  539. options.insertStyleElement = (insertStyleElement_default());
  540.  
  541. var update = injectStylesIntoStyleTag_default()(main/* default */.A, options);
  542.  
  543.  
  544.  
  545.  
  546. /* harmony default export */ const style_main = (main/* default */.A && main/* default */.A.locals ? main/* default */.A.locals : undefined);
  547.  
  548. ;// ./src/SettingsStorage.ts
  549. class SettingsStorage {
  550. /**
  551. * Initializes a new instance of the SettingsStorage class with the specified storage key.
  552. *
  553. * @param {string} storageKey - The key used to store the settings in the local storage.
  554. */
  555. constructor(storageKey) {
  556. this.storageKey = storageKey;
  557. }
  558. /**
  559. * Saves the given settings to the local storage.
  560. *
  561. * @param {any} settings - The settings to be saved.
  562. * @return {void} This function does not return anything.
  563. */
  564. saveSettings(settings) {
  565. localStorage.setItem(this.storageKey, JSON.stringify(settings));
  566. }
  567. /**
  568. * Loads the settings from the local storage.
  569. *
  570. * @return {any} The loaded settings, or null if no settings are found.
  571. */
  572. loadSettings() {
  573. const settings = localStorage.getItem(this.storageKey);
  574. return settings ? JSON.parse(settings) : null;
  575. }
  576. /**
  577. * Updates a specific setting in the local storage.
  578. *
  579. * @param {string} key - The key of the setting to update.
  580. * @param {any} value - The new value for the setting.
  581. * @return {void} This function does not return anything.
  582. */
  583. updateSetting(key, value) {
  584. const settings = this.loadSettings() || {};
  585. settings[key] = { ...(settings[key] || {}), ...value };
  586. this.saveSettings(settings);
  587. }
  588. /**
  589. * Retrieves a specific setting from the local storage.
  590. *
  591. * @param {string} key - The key of the setting to retrieve.
  592. * @return {any} The value of the setting, or null if the setting is not found.
  593. */
  594. getSetting(key) {
  595. const settings = this.loadSettings();
  596. return settings ? settings[key] : null;
  597. }
  598. /**
  599. * Removes a specific setting from the local storage.
  600. *
  601. * @param {string} key - The key of the setting to remove.
  602. * @return {void} This function does not return anything.
  603. */
  604. removeSetting(key) {
  605. const settings = this.loadSettings();
  606. if (settings && key in settings) {
  607. delete settings[key];
  608. this.saveSettings(settings);
  609. }
  610. }
  611. }
  612. SettingsStorage.instance = new SettingsStorage("WME_wazemySettings");
  613.  
  614. ;// ./src/plugins/PluginTooltip.ts
  615.  
  616.  
  617. class PluginTooltip {
  618. constructor() {
  619. this.sdk = unsafeWindow.getWmeSdk({
  620. scriptId: "wme-wazemy-tooltip",
  621. scriptName: "WazeMY",
  622. });
  623. this.initialize();
  624. }
  625. /**
  626. * Initializes the plugin by adding settings into the tab pane, setting the initial state of the settings based on the last stored value, and adding a hidden tooltip window.
  627. *
  628. * @return {void} This function does not return anything.
  629. */
  630. initialize() {
  631. // Add settings into tab pane.
  632. const settingsHTML = `<input type="checkbox" id="wazemySettings_tooltip_enable"/>
  633. <label for="wazemySettings_tooltip_enable">Enable map tooltip</label>`;
  634. $("#wazemySettings_settings").append(settingsHTML);
  635. $("#wazemySettings_tooltip_enable").on("change", () => {
  636. PluginManager.instance.updatePluginSettings("tooltip", {
  637. enable: $("#wazemySettings_tooltip_enable").prop("checked"),
  638. });
  639. });
  640. // Set settings according to last stored value.
  641. const savedSettings = SettingsStorage.instance.getSetting("tooltip");
  642. if (savedSettings?.enable === true) {
  643. $("#wazemySettings_tooltip_enable").prop("checked", true);
  644. }
  645. else {
  646. $("#wazemySettings_tooltip_enable").prop("checked", false);
  647. }
  648. // Add hidden tooltip window.
  649. const tooltipHTML = `<div id="wazemyTooltip"></div>`;
  650. $(document.body).append(tooltipHTML);
  651. console.log("[WazeMY] PluginTooltip initialized.");
  652. }
  653. /**
  654. * Enables the PluginTooltip by registering the "mousemove" event, showing the tooltip, and logging a message.
  655. *
  656. * @return {void} This function does not return anything.
  657. */
  658. enable() {
  659. // WazeWrap.Events.register("mousemove", null, this.showTooltip.bind(this));
  660. this.sdk.Events.on({
  661. eventName: "wme-map-mouse-move",
  662. eventHandler: showTooltip,
  663. });
  664. $("#wazemyTooltip").show();
  665. console.log("[WazeMY] PluginTooltip enabled.");
  666. }
  667. /**
  668. * Disables the PluginTooltip by unregistering the "mousemove" event, hiding the tooltip, and logging a message.
  669. *
  670. * @return {void} This function does not return anything.
  671. */
  672. disable() {
  673. // WazeWrap.Events.unregister("mousemove", null, this.showTooltip);
  674. this.sdk.Events.off({
  675. eventName: "wme-map-mouse-move",
  676. eventHandler: showTooltip,
  677. });
  678. $("#wazemyTooltip").hide();
  679. console.log("[WazeMY] PluginTooltip disabled.");
  680. }
  681. /**
  682. * Updates the settings of the PluginTooltip based on the provided settings object.
  683. *
  684. * @param {any} settings - The new settings object.
  685. * @return {void} This function does not return anything.
  686. */
  687. updateSettings(settings) {
  688. if (settings.enable === true) {
  689. this.enable();
  690. }
  691. else {
  692. this.disable();
  693. }
  694. console.log("[WazeMY] PluginTooltip settings updated.", settings);
  695. }
  696. }
  697. /**
  698. * Shows the tooltip at the mouse position.
  699. *
  700. * @return {void} This function does not return anything.
  701. */
  702. function showTooltip() {
  703. let output = "";
  704. let showTooltip = false;
  705. const sdk = unsafeWindow.getWmeSdk({
  706. scriptId: "wme-wazemy",
  707. scriptName: "WazeMY",
  708. });
  709. // Manual check of settings because unregistering event is not working.
  710. if ($("#wazemySettings_tooltip_enable").prop("checked") === true) {
  711. const landmark = W.map.venueLayer.getFeatureBy("renderIntent", "highlight");
  712. const segment = W.map.segmentLayer.getFeatureBy("renderIntent", "highlight");
  713. if (landmark) {
  714. const venue = sdk.DataModel.Venues.getById({
  715. venueId: landmark.attributes.wazeFeature.id,
  716. });
  717. output = venue.name ? `<b>${venue.name}</b><br>` : "";
  718. output += `<i>[${venue.categories.join(", ")}]</i><br>`;
  719. const venueAddress = sdk.DataModel.Venues.getAddress({
  720. venueId: landmark.attributes.wazeFeature.id,
  721. });
  722. output += venueAddress.houseNumber ? `${venueAddress.houseNumber}, ` : "";
  723. output += venueAddress.street.name
  724. ? `${venueAddress.street.name}<br>`
  725. : "";
  726. output += `${venueAddress.city.name}, ${venueAddress.state.name}<br>`;
  727. output += `<b>Lock:</b> ${venue.lockRank + 1}`;
  728. showTooltip = true;
  729. }
  730. else if (segment) {
  731. const segmentId = segment.attributes.wazeFeature.id;
  732. const segmentData = sdk.DataModel.Segments.getById({
  733. segmentId: segmentId,
  734. });
  735. const address = sdk.DataModel.Segments.getAddress({
  736. segmentId: segmentId,
  737. });
  738. output = address.street.name ? `<b>${address.street.name}</b><br>` : "";
  739. const altStreets = address.altStreets;
  740. for (let i = 0; i < altStreets.length; i++) {
  741. const altStreetName = altStreets[i].street.name;
  742. output += `Alt: ${altStreetName}<br>`;
  743. }
  744. output += `${address.city.name}, ${address.state.name}<br>`;
  745. output += `<b>ID:</b> ${segmentId}<br>`;
  746. if (segmentData.isTwoWay) {
  747. output += `<b>Direction:</b> Two way<br>`;
  748. }
  749. else if (segmentData.isAtoB) {
  750. output += `<b>Direction:</b> A -> B<br>`;
  751. }
  752. else if (segmentData.isBtoA) {
  753. output += `<b>Direction:</b> B -> A<br>`;
  754. }
  755. output += `<b>Lock:</b> ${segmentData.lockRank + 1}`;
  756. showTooltip = true;
  757. }
  758. const tooltipDiv = $("#wazemyTooltip");
  759. if (showTooltip === true) {
  760. let positions = [];
  761. positions = document
  762. .querySelector(".wz-map-ol-control-span-mouse-position")
  763. .innerHTML.split(" ");
  764. const lat = parseFloat(positions[0]);
  765. const lon = parseFloat(positions[1]);
  766. if (lat >= 0 && lon >= 0) {
  767. let pixel = sdk.Map.getPixelFromLonLat({
  768. lonLat: {
  769. lat: parseFloat(positions[0]),
  770. lon: parseFloat(positions[1]),
  771. },
  772. });
  773. const tw = tooltipDiv.innerWidth();
  774. const th = tooltipDiv.innerHeight();
  775. let tooltipX = pixel.x + window.scrollX + 15;
  776. let tooltipY = pixel.y + window.scrollY + 15;
  777. // Handle cases where tooltip is too near the edge.
  778. if (tooltipX + tw > W.map.$map.innerWidth()) {
  779. tooltipX -= tw + 20; // 20 = scroll bar size
  780. if (tooltipX < 0) {
  781. tooltipX = 0;
  782. }
  783. }
  784. if (tooltipY + th > W.map.$map.innerHeight()) {
  785. tooltipY -= th + 20;
  786. if (tooltipY < 0) {
  787. tooltipY = 0;
  788. }
  789. }
  790. tooltipDiv.html(output);
  791. tooltipDiv.css("top", `${tooltipY}px`);
  792. tooltipDiv.css("left", `${tooltipX}px`);
  793. tooltipDiv.css("visibility", "visible");
  794. }
  795. }
  796. else {
  797. tooltipDiv.css("visibility", "hidden");
  798. }
  799. }
  800. }
  801.  
  802. ;// ./src/plugins/PluginCopyLatLon.ts
  803. class PluginCopyLatLon {
  804. constructor() {
  805. this.sdk = unsafeWindow.getWmeSdk({
  806. scriptId: "wme-wazemy-copylatlon",
  807. scriptName: "WazeMY",
  808. });
  809. this.initialize();
  810. }
  811. /**
  812. * Initialize plugin.
  813. *
  814. * @return {void} This function does not return anything.
  815. */
  816. initialize() {
  817. const settingsHTML = `<div>Ctrl+Alt+C: <i>Copy lat/lon of mouse position to clipboard.</i></div>`;
  818. $("#wazemySettings_shortcuts").append(settingsHTML);
  819. this.enable(); // Manually enable plugin since there is no settings to trigger this.
  820. console.log("[WazeMY] PluginCopyLatLon initialized.");
  821. }
  822. /**
  823. * Enable plugin.
  824. *
  825. * @return {void} This function does not return anything.
  826. */
  827. enable() {
  828. const shortcut = {
  829. callback: this.copyLatLon,
  830. description: "Copy lat/lon of mouse position to clipboard.",
  831. shortcutId: "WazeMY_latloncopy",
  832. shortcutKeys: "CA+c",
  833. };
  834. this.sdk.Shortcuts.createShortcut(shortcut);
  835. console.log("[WazeMY] PluginCopyLatLon enabled.");
  836. }
  837. /**
  838. * Disable plugin.
  839. *
  840. * @return {void} This function does not return anything.
  841. */
  842. disable() {
  843. console.log("[WazeMY] PluginCopyLatLon disabled.");
  844. }
  845. /**
  846. * Updates the settings of the PluginCopyLatLon based on the provided settings object.
  847. *
  848. * @return {void} This function does not return anything.
  849. */
  850. updateSettings(settings) {
  851. console.log("[WazeMY] PluginCopyLatLon settings updated.", settings);
  852. }
  853. /**
  854. * Copies lat/lon of mouse position to clipboard.
  855. *
  856. * @return {void} This function does not return anything.
  857. */
  858. copyLatLon() {
  859. console.log("[WazeMY] Copy lat/lon shortcut triggered.");
  860. const latlon = $(".wz-map-ol-control-span-mouse-position").text();
  861. navigator.clipboard.writeText(latlon);
  862. }
  863. }
  864.  
  865. ;// ./src/plugins/PluginTrafficCameras.ts
  866.  
  867.  
  868. class PluginTrafficCameras {
  869. constructor() {
  870. this.initialize();
  871. }
  872. initialize() {
  873. // Add settings into view.
  874. const settingsHTML = `<div>
  875. <input type="checkbox" id="wazemySettings_trafcam_enable" style="margin-top:0px"/>
  876. <label for="wazemySettings_trafcam_enable">Traffic cameras</label><br></div>`;
  877. const wazemySettings = document.getElementById("wazemySettings_settings");
  878. $("#wazemySettings_settings").append(settingsHTML);
  879. // wazemySettings.insertAdjacentHTML("afterbegin", settingsHTML);
  880. const settingsEl = document.getElementById(`wazemySettings_trafcam_enable`);
  881. const savedSettings = SettingsStorage.instance.getSetting("trafcam");
  882. if (savedSettings?.enable === true) {
  883. settingsEl.checked = true;
  884. }
  885. else {
  886. settingsEl.checked = false;
  887. }
  888. settingsEl.onchange = (e) => {
  889. const target = e.target;
  890. PluginManager.instance.updatePluginSettings("trafcam", {
  891. enable: target.checked,
  892. });
  893. };
  894. // Install camera icon
  895. if (!OpenLayers.Icon) {
  896. this.installIconClass();
  897. }
  898. this.trafcamLayer = new OpenLayers.Layer.Markers("wazemyTrafcamLayer");
  899. W.map.addLayer(this.trafcamLayer);
  900. this.showIcons();
  901. console.log("PluginTrafficCameras initialized.");
  902. }
  903. enable() {
  904. console.log("PluginTrafficCameras enabled.");
  905. this.trafcamLayer.setVisibility(true);
  906. }
  907. disable() {
  908. console.log("PluginTrafficCameras disabled.");
  909. this.trafcamLayer.setVisibility(false);
  910. }
  911. updateSettings(settings) {
  912. if (settings.enable === true) {
  913. this.enable();
  914. }
  915. else {
  916. this.disable();
  917. }
  918. console.log("PluginTrafficCameras settings updated", settings);
  919. }
  920. installIconClass() {
  921. OpenLayers.Icon = OpenLayers.Class({
  922. url: null,
  923. size: null,
  924. offset: null,
  925. calculateOffset: null,
  926. imageDiv: null,
  927. px: null,
  928. initialize: function (url, size, offset, calculateOffset) {
  929. this.url = url;
  930. this.size = size || { w: 20, h: 20 };
  931. this.offset = offset || {
  932. x: -(this.size.w / 2),
  933. y: -(this.size.h / 2),
  934. };
  935. this.calculateOffset = calculateOffset;
  936. url = OpenLayers.Util.createUniqueID("OL_Icon_");
  937. const div = (this.imageDiv = OpenLayers.Util.createAlphaImageDiv(url));
  938. $(div.firstChild).removeClass("olAlphaImg"); // LEAVE THIS LINE TO PREVENT WME-HARDHATS SCRIPT FROM TURNING ALL ICONS INTO HARDHAT WAZERS --MAPOPMATIC
  939. },
  940. destroy: function () {
  941. this.erase();
  942. OpenLayers.Event.stopObservingElement(this.imageDiv.firstChild);
  943. this.imageDiv.innerHTML = "";
  944. this.imageDiv = null;
  945. },
  946. clone: function () {
  947. return new OpenLayers.Icon(this.url, this.size, this.offset, this.calculateOffset);
  948. },
  949. setSize: function (size) {
  950. null !== size && (this.size = size);
  951. this.draw();
  952. },
  953. setUrl: function (url) {
  954. null !== url && (this.url = url);
  955. this.draw();
  956. },
  957. draw: function (a) {
  958. OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, null, this.size, this.url, "absolute");
  959. this.moveTo(a);
  960. return this.imageDiv;
  961. },
  962. erase: function () {
  963. null !== this.imageDiv &&
  964. null !== this.imageDiv.parentNode &&
  965. OpenLayers.Element.remove(this.imageDiv);
  966. },
  967. setOpacity: function (a) {
  968. OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, null, null, null, null, null, null, a);
  969. },
  970. moveTo: function (a) {
  971. null !== a && (this.px = a);
  972. null !== this.imageDiv &&
  973. (null === this.px
  974. ? this.display(!1)
  975. : (this.calculateOffset &&
  976. (this.offset = this.calculateOffset(this.size)),
  977. OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, {
  978. x: this.px.x + this.offset.x,
  979. y: this.px.y + this.offset.y,
  980. })));
  981. },
  982. display: function (a) {
  983. this.imageDiv.style.display = a ? "" : "none";
  984. },
  985. isDrawn: function () {
  986. return (this.imageDiv &&
  987. this.imageDiv.parentNode &&
  988. 11 != this.imageDiv.parentNode.nodeType);
  989. },
  990. CLASS_NAME: "OpenLayers.Icon",
  991. });
  992. }
  993. showIcons() {
  994. trafficCamsData.forEach((e, idx) => {
  995. this.drawCamIcon({
  996. idx: idx,
  997. desc: e.desc,
  998. src: e.url,
  999. width: 20,
  1000. height: 20,
  1001. lat: e.lat,
  1002. lon: e.lon,
  1003. });
  1004. });
  1005. }
  1006. drawCamIcon(spec) {
  1007. const camIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAAGXcA1uAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpBRDNGNTkwRTYzQThFMzExQTc4MDhDNjAwODdEMzdEQSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2OUI0RUEyN0IwRjcxMUUzOERFM0E1OTJCRUY3NTFBOCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2OUI0RUEyNkIwRjcxMUUzOERFM0E1OTJCRUY3NTFBOCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgV2luZG93cyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjZGOEJBMzExNkZCMEUzMTFCOEY5QTU3QUQxM0M2MjI5IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkFEM0Y1OTBFNjNBOEUzMTFBNzgwOEM2MDA4N0QzN0RBIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+TV0cjwAABbhJREFUeNpiYIAAXiZGCIMPiP//f8DwnwnI+PT/HZD8y8AAEEBQVQzTwOSfuwz/u6qAyh4y/Gf8f4/hPwMnUJSZgQEggMCyzpZAmbsILMTHcAokPhPEWT8dqJoBgvetAtMMDBIiDCf/P4bqeMXwf+t8BiOAAGJAAqVA7A1iPH/+goFBT4OB8f9LJDueAzFQgOHGLoTZYEGgpJgww0+G/68ZQArAEsryEHpRN5BuK4FIcHMhjJORVTvBYG3MwPD2CkKwKAVI/3nBABBAYOcY6zKAjGSQEmP4cGkXxMlgK4BeaCll+B/lzzDh/yegmr8vIO4HGv/l/0eG/w4WDP9fnUM4Dhl/uMzwf/6CFQ4g9RUgE3ctRvgGGSvJMfw/tw3i7sY8hv8sQMGrQE8yuFoBZe8CeRwMDIKaDAyWRgwM2xYD+exA/AuIvwAV3kaE6sSPQCuBHv2vqczwf0YL0MR7SE4CsgsTGP5///aSCZQSGDRVGPJfv2dgNDNkcP30heHbB6BpyzYyMCRVMjCwqDIcOX+LgTEtioGRhfn/P4AAbJTfK0NhGMe/Z+ecpVkrScnIlCiWkoulpFyhxgXKXCGK3XGBG/4BJcoNN665tqsxo6XshiKtyQybn82vMZPF63nPOXKYi+fieU7P8z59P9/n6NlBVNrRRDFPMUlRIGhmM0gWzk5NSCFMuDE2MqAaUJGUhDgOgNmKkXmLwMRudA11diynI9lSKhEHG+oBu9q1mHkDf9AWWkM0dgHEb4H+PqqkKD51uxpJuSoDHpIfAowyohyUveJH+9pqUjigav/9UnIfzO/frkRvBxUuwcpK/gc3PkzfzynuwRoanYuStZDKaeAkwA8QEPJ/CYfpBUCmqxp1E7uXJ6u0tUNVQbvIR+DIBzgHgQMvrZ5LZWIiSuowh6N+nQ9ZYgnNcLTzdRBsz5Ot1socWCr1KipYulrJVDIQjqjwgqsESvcPQB5QWmP2nsWem5X80IeizhaadPfHQwTxnXJTDk5ZQgeOOCC0ScY0wtPdRrc4AzY7BVZuQ8bVDhcXJLyhNnwJUFj5hTQVhmH8mQ7H5nYYkRxJw8hqBWYsLIr+gisKYobdjKguClOKLiQvgrrwqogiIr1wECHdRCCjIopNiCKZIGkthysrrWSklg36M6O5fT3fzmk7C8kDL4zt2/neP8/ze01/b6XuceWsVmAJJR56gurObjSn0/DWrEY152SmIyFBNk1sxZhBZAQTyVmEDjeiy9eAQdm4FK1gLlHg8Y3CYlXzZW12A1+GYd1Yi1u1LogXQSYgmxdnjM8jsTFNZvLMxADE3h0QS8vxNNqLJWKa2ZMXBRXwaaNmL/XdoUct+hhtjF+MFBZ+zJq5Gw6ywoRyI/xs/JjJtCj38+XWo3rGdM6pI3nlqYshmmiGx7fJpLE8rAqGZQy+49o5CNeauoeplJZZPbWfztqSWurvmV/ixrCXQjRSFQE/xI8R/dK44VJae99OorWt/Xh2tZxp0Q+903zynX/q6YLwevgy28IXyiBYxCY3cXYeIvGGRL0Isc697Z5k0NRMQj8Grd92zuDALuAuIRm8CVSohe1uYZ+T74FwADjdBFBh6GgH+mm3ZuLDSb9Ocg9YLNYZOeSyUitevupFeWWVTlzjQ0dNfQJW1fOybulPWlmyZ85wpshQC43/m+9YsR2ZTn9wg5z955+z2FO3H0PRIIqcHHzgPpAgNukwOJzAwCB3NiFQxsyyjJ37J4lMXklpfnaRyidaO3xe7+6h3JmVy2CjkcInD3EO3/T1xsFn3mobX0z+RzkfNAxcpXrIjo+RkFKp0VLkk1hfwwqJr69RODxbcH05gem/wIEN62TV13XuMxNIvqYYqCT6R6x14UHsEarkwhBxJbtnC4wmS9fXDuT2stFkxIDsp4tfbZU5MCr0jnMbIMLoKy7Gc8WunU3rxHMoCmKxUaiqij/5alOWhMPoGAAAAABJRU5ErkJggg==";
  1008. const size = new OpenLayers.Size(20, 20);
  1009. const icon = new OpenLayers.Icon(camIcon, size);
  1010. const epsg4326 = new OpenLayers.Projection("EPSG:4326"); // WGS 1984 projection. Malaysia uses EPSG:900913
  1011. const projectTo = W.map.getProjectionObject();
  1012. const lonLat = new OpenLayers.LonLat(spec.lon, spec.lat).transform(epsg4326, projectTo);
  1013. const newMarker = new OpenLayers.Marker(lonLat, icon);
  1014. newMarker.idx = spec.idx;
  1015. newMarker.title = spec.desc;
  1016. newMarker.url = spec.src;
  1017. newMarker.width = spec.width;
  1018. newMarker.height = spec.height;
  1019. newMarker.location = lonLat;
  1020. newMarker.events.register("click", newMarker, this.popupCam);
  1021. this.trafcamLayer.addMarker(newMarker);
  1022. }
  1023. popupCam(e) {
  1024. popupCam_close(); // Close existing popup if already opened.
  1025. var popupHTML = `<div id="gmPopupContainerCam" style="margin:1;text-align:center;padding:5px;z-index:1100;position:absolute;color:white;background:rgba(0,0,0,0.5)">
  1026. <table border=0>
  1027. <tr>
  1028. <td><div id="mycamdivheader" style="min-height:20px;white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;width:380px">${e.object.title}</div></td>
  1029. <td align="right"><a href="#close" id="gmCloseCamDlgBtn" title="Close" style="color:red">X</a></td>
  1030. </tr>
  1031. <tr><td colspan=2>Select source:
  1032. <select id="wazemy_camSource">
  1033. </select>
  1034. <div hidden id="mycamid">${e.object.idx}</div>
  1035. </td></tr>
  1036. <tr><td colspan=2><img style="width:400px" id="staticimage"></td></tr>
  1037. <tr><td colspan=2><div id="mycamstatus"></div></td></tr>
  1038. </table></div>`;
  1039. document.body.insertAdjacentHTML("afterbegin", popupHTML);
  1040. // Handle cases where popup is too near the edge.
  1041. let tw = $("#gmPopupContainerCam").width();
  1042. let th = $("#gmPopupContainerCam").height() + 200;
  1043. var tooltipX = e.clientX + window.scrollX + 15;
  1044. var tooltipY = e.clientY + window.scrollY + 15;
  1045. if (tooltipX + tw > W.map.$map.innerWidth()) {
  1046. tooltipX -= tw + 20; // 20 = scroll bar size
  1047. if (tooltipX < 0)
  1048. tooltipX = 0;
  1049. }
  1050. if (tooltipY + th > W.map.$map.innerHeight()) {
  1051. tooltipY -= th + 20;
  1052. if (tooltipY < 0)
  1053. tooltipY = 0;
  1054. }
  1055. $("#gmPopupContainerCam").css({ left: tooltipX });
  1056. $("#gmPopupContainerCam").css({ top: tooltipY });
  1057. //Add listener for popup's "Close" button
  1058. const closeBtn = document.getElementById("gmCloseCamDlgBtn");
  1059. closeBtn.onclick = popupCam_close;
  1060. // Allow popup to be draggable.
  1061. const popupContainerEl = document.getElementById("gmPopupContainerCam");
  1062. popup_dragElement(popupContainerEl);
  1063. const camSourceEl = document.getElementById("wazemy_camSource");
  1064. for (let urlsrc in e.object.url) {
  1065. if (urlsrc === "LLM" && e.object.url["LLM"].split("|").length == 2) {
  1066. popup_appendOption(urlsrc);
  1067. }
  1068. else if (urlsrc === "Jalanow") {
  1069. popup_appendOption(urlsrc);
  1070. }
  1071. }
  1072. camSourceEl.onchange = (e) => {
  1073. console.log("PluginTrafficCameras: Camera source selection changed.");
  1074. const camId = document.getElementById("mycamid");
  1075. const target = e.target;
  1076. switch (target.selectedOptions[0].innerText) {
  1077. case "Jalanow":
  1078. popup_getJalanowImage(trafficCamsData[camId.innerText]["url"]["Jalanow"]);
  1079. break;
  1080. case "LLM":
  1081. popup_getLLMImage(trafficCamsData[camId.innerText]["url"]["LLM"]);
  1082. break;
  1083. }
  1084. };
  1085. // Get image for the first time when popup is displayed.
  1086. switch (Object.keys(e.object.url)[0]) {
  1087. case "Jalanow":
  1088. popup_getJalanowImage(e.object.url["Jalanow"]);
  1089. break;
  1090. case "LLM":
  1091. popup_getLLMImage(e.object.url["LLM"]);
  1092. break;
  1093. }
  1094. function popupCam_close() {
  1095. const popupContainerEl = document.getElementById("gmPopupContainerCam");
  1096. if (popupContainerEl) {
  1097. popupContainerEl.remove();
  1098. popupContainerEl.hidden = true;
  1099. }
  1100. }
  1101. function popup_dragElement(elmnt) {
  1102. var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  1103. if (document.getElementById("mycamdivheader")) {
  1104. // if present, the header is where you move the DIV from:
  1105. document.getElementById("mycamdivheader").onmousedown = dragMouseDown;
  1106. }
  1107. else {
  1108. // otherwise, move the DIV from anywhere inside the DIV:
  1109. elmnt.onmousedown = dragMouseDown;
  1110. }
  1111. function dragMouseDown(e) {
  1112. e.preventDefault();
  1113. // get the mouse cursor position at startup:
  1114. pos3 = e.clientX;
  1115. pos4 = e.clientY;
  1116. document.onmouseup = closeDragElement;
  1117. // call a function whenever the cursor moves:
  1118. document.onmousemove = elementDrag;
  1119. }
  1120. function elementDrag(e) {
  1121. e.preventDefault();
  1122. // calculate the new cursor position:
  1123. pos1 = pos3 - e.clientX;
  1124. pos2 = pos4 - e.clientY;
  1125. pos3 = e.clientX;
  1126. pos4 = e.clientY;
  1127. // set the element's new position:
  1128. const popupContainerEl = document.getElementById("gmPopupContainerCam");
  1129. popupContainerEl.style.top = popupContainerEl.offsetTop - pos2 + "px";
  1130. popupContainerEl.style.left = popupContainerEl.offsetLeft - pos1 + "px";
  1131. }
  1132. function closeDragElement() {
  1133. // stop moving when mouse button is released:
  1134. document.onmouseup = null;
  1135. document.onmousemove = null;
  1136. }
  1137. }
  1138. function popup_appendOption(urlsrc) {
  1139. const option = document.createElement("option");
  1140. option.value = urlsrc;
  1141. option.text = urlsrc;
  1142. camSourceEl.append(option);
  1143. }
  1144. function popup_getJalanowImage(url) {
  1145. GM_xmlhttpRequest({
  1146. method: "GET",
  1147. responseType: "blob",
  1148. headers: {
  1149. authority: "p4.fgies.com",
  1150. referer: "https://www.jalanow.com/",
  1151. accept: "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
  1152. },
  1153. url: url,
  1154. onload: function (response) {
  1155. const staticImageEl = document.getElementById("staticimage");
  1156. staticImageEl.src = URL.createObjectURL(response.response);
  1157. document.getElementById("mycamstatus").innerHTML = "";
  1158. },
  1159. onerror: function (response) {
  1160. document.getElementById("mycamstatus").innerHTML =
  1161. "Error loading image.";
  1162. },
  1163. onprogress: function (response) {
  1164. document.getElementById("mycamstatus").innerHTML = "Loading image...";
  1165. },
  1166. });
  1167. }
  1168. function popup_getLLMImage(url) {
  1169. let camImg = url.split("|");
  1170. GM_xmlhttpRequest({
  1171. method: "GET",
  1172. responseType: "blob",
  1173. url: camImg[0],
  1174. onload: function (response) {
  1175. const re = new RegExp('src="data:image/png;base64, ([A-Za-z0-9/+=]*)" title="' +
  1176. camImg[1] +
  1177. '"');
  1178. const m = response.responseText.match(re);
  1179. const staticImageEl = document.getElementById("staticimage");
  1180. staticImageEl.src = "data:image/png;base64," + m[1];
  1181. document.getElementById("mycamstatus").innerHTML = "";
  1182. },
  1183. onerror: function (response) {
  1184. document.getElementById("mycamstatus").innerHTML =
  1185. "Error loading image.";
  1186. },
  1187. onprogress: function (response) {
  1188. document.getElementById("mycamstatus").innerHTML = "Loading image...";
  1189. },
  1190. });
  1191. }
  1192. }
  1193. }
  1194.  
  1195. ;// ./src/plugins/PluginKVMR.ts
  1196.  
  1197.  
  1198. class PluginKVMR {
  1199. constructor() {
  1200. this.areas = [
  1201. {
  1202. name: "Area 1",
  1203. geometry: "POLYGON ((101.296968 3.210365, 101.30376 3.118812, 101.327352 3.100621, 101.332475 3.083048, 101.3447571 3.0671528, 101.3475037 2.9992702, 101.4546204 2.9862417, 101.443634 3.2097608, 101.296968 3.210365))",
  1204. color: "#ffffff",
  1205. },
  1206. {
  1207. name: "Area 2a",
  1208. geometry: "POLYGON ((101.443634 3.2097608, 101.4546204 2.9862417, 101.542797963266 2.999451622765, 101.546214771469 3.20939581928522, 101.443634 3.2097608))",
  1209. color: "#ff0000",
  1210. },
  1211. {
  1212. name: "Area 2b",
  1213. geometry: "POLYGON ((101.546208402411 3.20940523257651, 101.545212203573 3.14955753765693, 101.640751782444 3.14826179285013, 101.6379547 3.2090753, 101.546208402411 3.20940523257651))",
  1214. color: "#00ff00",
  1215. },
  1216. {
  1217. name: "Area 2c",
  1218. geometry: "POLYGON ((101.545226515908 3.14956187716133, 101.544049591624 3.0763653393285, 101.643784992874 3.08223941174546, 101.640750558754 3.14825692296985, 101.545226515908 3.14956187716133))",
  1219. color: "#0000ff",
  1220. },
  1221. {
  1222. name: "Area 2d",
  1223. geometry: "POLYGON ((101.544050641663 3.07636531606873, 101.542795175242 2.99943779058738, 101.6468811 3.0150413, 101.643784373198 3.08223705403019, 101.544050641663 3.07636531606873))",
  1224. color: "#ffff00",
  1225. },
  1226. {
  1227. name: "Area 3",
  1228. geometry: "POLYGON ((101.6379547 3.2090753, 101.6412146 3.145488, 101.7443848 3.1501147, 101.7309414 3.2090495, 101.6379547 3.2090753))",
  1229. color: "#ff00ff",
  1230. },
  1231. {
  1232. name: "Area 4",
  1233. geometry: "POLYGON ((101.6412146 3.145488, 101.6439612 3.0796673, 101.7615509 3.0791519, 101.7443848 3.1501147, 101.6412146 3.145488))",
  1234. color: "#00ffff",
  1235. },
  1236. {
  1237. name: "Area 5",
  1238. geometry: "POLYGON ((101.788224 3.210399, 101.7309414 3.2090495, 101.7615509 3.0791519, 101.6439612 3.0796673, 101.6468811 3.0150413, 101.8391418 3.0253266, 101.788224 3.210399))",
  1239. color: "#40ff00",
  1240. },
  1241. {
  1242. name: "Area 6",
  1243. geometry: "POLYGON ((101.332475 3.083048, 101.262098 3.074553, 101.2335205 3.0815516, 101.208976 3.060844, 101.162394 2.989639, 101.223721 2.903504, 101.268598 2.871386, 101.284902 2.830662, 101.448138 2.72835, 101.4546204 2.9862417, 101.3475037 2.9992702, 101.3447571 3.0671528, 101.332475 3.083048))",
  1244. color: "#ff4000",
  1245. },
  1246. {
  1247. name: "Area 7",
  1248. geometry: "POLYGON ((101.4546204 2.9862417, 101.448138 2.72835, 101.477474 2.766274, 101.559411 2.807463, 101.6578674 2.8223442, 101.6468811 3.0150413, 101.4546204 2.9862417))",
  1249. color: "#33ff00",
  1250. },
  1251. {
  1252. name: "Area 8",
  1253. geometry: "POLYGON ((101.6578674 2.8223442, 101.725067 2.83344, 101.756459 2.866068, 101.882828 2.870563, 101.8391418 3.0253266, 101.6468811 3.0150413, 101.6578674 2.8223442))",
  1254. color: "#ff0033",
  1255. },
  1256. ];
  1257. this.initialize();
  1258. }
  1259. initialize() {
  1260. // Add settings into view.
  1261. const settingsHTML = `<div>
  1262. <input type="checkbox" id="wazemySettings_kvmr_enable" style="margin-top:0px"/>
  1263. <label for="wazemySettings_kvmr_enable">Klang Valley Map Raid</label><br></div>`;
  1264. $("#wazemySettings_settings").append(settingsHTML);
  1265. const settingsEl = document.getElementById(`wazemySettings_kvmr_enable`);
  1266. const savedSettings = SettingsStorage.instance.getSetting("kvmr");
  1267. if (savedSettings?.enable === true) {
  1268. settingsEl.checked = true;
  1269. }
  1270. else {
  1271. settingsEl.checked = false;
  1272. }
  1273. settingsEl.onchange = (e) => {
  1274. const target = e.target;
  1275. PluginManager.instance.updatePluginSettings("kvmr", {
  1276. enable: target.checked,
  1277. });
  1278. };
  1279. // Add MR polygon overlay.
  1280. const mro_Map = W.map;
  1281. const mro_OL = OpenLayers;
  1282. // const mro_mapLayers = mro_Map.getLayersBy("uniqueName", "__KlangValley");
  1283. this.raid_mapLayer = new mro_OL.Layer.Vector("KlangValley", {
  1284. displayInLayerSwitcher: true,
  1285. uniqueName: "__KlangValley",
  1286. });
  1287. mro_Map.addLayer(this.raid_mapLayer);
  1288. this.areas.forEach((area) => {
  1289. const geometry = parseWKT(area.geometry);
  1290. this.addRaidPolygon(this.raid_mapLayer, geometry, area.color, area.name);
  1291. });
  1292. mro_Map.events.register("moveend", W.map, function () {
  1293. currentRaidLocation();
  1294. });
  1295. mro_Map.events.register("zoomend", W.map, function () {
  1296. currentRaidLocation();
  1297. });
  1298. console.log("PluginKVMR initialized.");
  1299. /**
  1300. * Updates the current raid location on the map based on the user's current location.
  1301. *
  1302. * @return {void} This function does not return anything.
  1303. */
  1304. function currentRaidLocation() {
  1305. // Only run if the plugin is enabled. Workaround because unregistering events doesn't work.
  1306. if ($("#wazemySettings_kvmr_enable").is(":checked") === false) {
  1307. return;
  1308. }
  1309. var mro_Map = W.map;
  1310. const mro_mapLayers = mro_Map.getLayersBy("uniqueName", "__KlangValley")[0];
  1311. for (let i = 0; i < mro_mapLayers.features?.length; i++) {
  1312. var raidMapCenter = mro_Map.getCenter();
  1313. var raidCenterPoint = new OpenLayers.Geometry.Point(raidMapCenter.lon, raidMapCenter.lat);
  1314. const raid_mapLayer = mro_Map.getLayersBy("uniqueName", "__KlangValley")[0];
  1315. var raidCenterCheck = raid_mapLayer.features[i].geometry.components[0].containsPoint(raidCenterPoint);
  1316. var holes = raid_mapLayer.features[i].attributes.holes;
  1317. if (raidCenterCheck === true) {
  1318. var str = $("#topbar-container > div > div.location-info-region > div").text();
  1319. const location = str.split(" - ");
  1320. if (location.length > 1) {
  1321. location[1] =
  1322. "Klang Valley MapRaid " +
  1323. raid_mapLayer.features[i].attributes.number;
  1324. }
  1325. else {
  1326. location.push("Klang Valley MapRaid " +
  1327. raid_mapLayer.features[i].attributes.number);
  1328. }
  1329. const raidLocationLabel = location.join(" - ");
  1330. setTimeout(function () {
  1331. $("#topbar-container > div > div.location-info-region > div").text(raidLocationLabel);
  1332. }, 200);
  1333. if (holes === "false") {
  1334. break;
  1335. }
  1336. }
  1337. }
  1338. }
  1339. function parseWKT(wkt) {
  1340. let trimmed;
  1341. if (wkt.startsWith("POLYGON")) {
  1342. trimmed = wkt.replace("POLYGON ((", "").replace("))", "");
  1343. }
  1344. const coordinatePairs = trimmed.split(", ");
  1345. const coordinates = coordinatePairs.map((pair) => {
  1346. const [lon, lat] = pair.split(" ");
  1347. return { lon, lat };
  1348. });
  1349. return coordinates;
  1350. }
  1351. }
  1352. enable() {
  1353. this.raid_mapLayer.setVisibility(true);
  1354. console.log("PluginKVMR enabled.");
  1355. }
  1356. disable() {
  1357. this.raid_mapLayer.setVisibility(false);
  1358. const mro_map = W.map;
  1359. mro_map.events.unregister("moveend", W.map);
  1360. mro_map.events.unregister("zoomend", W.map);
  1361. console.log("PluginKVMR disabled.");
  1362. }
  1363. updateSettings(settings) {
  1364. if (settings.enable === true) {
  1365. this.enable();
  1366. }
  1367. else {
  1368. this.disable();
  1369. }
  1370. console.log("PluginKVMR settings updated", settings);
  1371. }
  1372. addRaidPolygon(raidLayer, groupPoints, groupColor, groupNumber) {
  1373. var mro_Map = W.map;
  1374. var mro_OL = OpenLayers;
  1375. var raidGroupLabel = "KlangValley " + groupNumber;
  1376. var groupName = "RaidGroup " + groupNumber;
  1377. var style = {
  1378. strokeColor: groupColor,
  1379. strokeOpacity: 0.8,
  1380. strokeWidth: 3,
  1381. fillColor: groupColor,
  1382. fillOpacity: 0.15,
  1383. label: raidGroupLabel,
  1384. labelOutlineColor: "black",
  1385. labelOutlineWidth: 3,
  1386. fontSize: 14,
  1387. fontColor: groupColor,
  1388. fontOpacity: 0.85,
  1389. fontWeight: "bold",
  1390. };
  1391. var attributes = {
  1392. name: groupName,
  1393. number: groupNumber,
  1394. };
  1395. var pnt = [];
  1396. for (let i = 0; i < groupPoints.length; i++) {
  1397. const convPoint = new OpenLayers.Geometry.Point(groupPoints[i].lon, groupPoints[i].lat).transform(new OpenLayers.Projection("EPSG:4326"), mro_Map.getProjectionObject());
  1398. //console.log('MapRaid: ' + JSON.stringify(groupPoints[i]) + ', ' + groupPoints[i].lon + ', ' + groupPoints[i].lat);
  1399. pnt.push(convPoint);
  1400. }
  1401. var ring = new mro_OL.Geometry.LinearRing(pnt);
  1402. var polygon = new mro_OL.Geometry.Polygon([ring]);
  1403. var feature = new mro_OL.Feature.Vector(polygon, attributes, style);
  1404. raidLayer.addFeatures([feature]);
  1405. }
  1406. }
  1407.  
  1408. ;// ./src/plugins/PluginZoomPic.ts
  1409. class PluginZoomPic {
  1410. constructor() {
  1411. this.initialize();
  1412. }
  1413. /**
  1414. * Initialize plugin.
  1415. *
  1416. * @return {void} This function does not return anything.
  1417. */
  1418. initialize() {
  1419. $(document.body).on("click", () => {
  1420. const img = $(".venue-image-dialog > wz-dialog-content > img");
  1421. if (img.length > 0) {
  1422. const newImg = img[0];
  1423. const links = $(".venue-image-dialog > wz-dialog-header > a");
  1424. for (let i = 0; i < links.length; i++) {
  1425. links[i].remove();
  1426. }
  1427. const newImgHTML = `<a href="${newImg.src.replace("thumbs/thumb700_", "")}" target="_blank">(+)</a>`;
  1428. $("wz-dialog-header").append(newImgHTML);
  1429. }
  1430. // $("div.modal-dialog.venue-image-dialog");
  1431. });
  1432. console.log("[WazeMY] PluginZoomPic initialized.");
  1433. }
  1434. /**
  1435. * Enable plugin.
  1436. *
  1437. * @return {void} This function does not return anything.
  1438. */
  1439. enable() {
  1440. console.log("[WazeMY] PluginZoomPic enabled.");
  1441. }
  1442. /**
  1443. * Disable plugin.
  1444. *
  1445. * @return {void} This function does not return anything.
  1446. */
  1447. disable() {
  1448. console.log("[WazeMY] PluginZoomPic disabled.");
  1449. }
  1450. /**
  1451. * Updates the settings of the PluginZoomPic based on the provided settings object.
  1452. *
  1453. * @return {void} This function does not return anything.
  1454. */
  1455. updateSettings(settings) {
  1456. console.log("[WazeMY] PluginZoomPic settings updated.", settings);
  1457. }
  1458. }
  1459.  
  1460. ;// ./src/plugins/PluginPlaces.ts
  1461.  
  1462.  
  1463. class PluginPlaces {
  1464. constructor() {
  1465. this.tabHTML = `
  1466. <div><h4>WazeMY Places</h4></div>
  1467. <div id="wazemyPlaces">
  1468. <select name="wazemyPlaces_polygons" id="wazemyPlaces_polygons"></select>
  1469. <button id="wazemyPlaces_scan">Scan</button>
  1470. <div id="wazemyPlaces_scanStatus"></div>
  1471. <div id="wazemyPlaces_purCount"></div>
  1472. <div id="wazemyPlaces_totalCount"></div>
  1473. <div id="wazemyPlaces_table">
  1474. <table id="wazemyPlaces_venues">
  1475. <thead>
  1476. <tr>
  1477. <th title="I=Image\nN=New Place\nU=Update\nF=Flag\nD=Delete">PUR</th>
  1478. <th>L</th>
  1479. <th>Name</th>
  1480. <th>Errors</th>
  1481. </tr>
  1482. </thead>
  1483. <tbody></tbody>
  1484. </table>
  1485. </div>
  1486. </div>
  1487. `;
  1488. this.initialize();
  1489. }
  1490. /**
  1491. * Initialize plugin.
  1492. *
  1493. * @return {void} This function does not return anything.
  1494. */
  1495. initialize() {
  1496. const settingsHTML = `<div><input type="checkbox" id="wazemySettings_places_enable"/>
  1497. <label for="wazemySettings_places_enable">Enable Places</label></div>`;
  1498. $("#wazemySettings_settings").append(settingsHTML);
  1499. $("#wazemySettings_places_enable").on("change", () => {
  1500. PluginManager.instance.updatePluginSettings("places", {
  1501. enable: $("#wazemySettings_places_enable").prop("checked"),
  1502. });
  1503. });
  1504. // Set settings according to last stored value.
  1505. const savedSettings = SettingsStorage.instance.getSetting("places");
  1506. if (savedSettings?.enable === true) {
  1507. $("#wazemySettings_places_enable").prop("checked", true);
  1508. }
  1509. else {
  1510. $("#wazemySettings_places_enable").prop("checked", false);
  1511. }
  1512. console.log("[WazeMY] PluginPlaces initialized.");
  1513. }
  1514. /**
  1515. * Enable plugin.
  1516. *
  1517. * @return {void} This function does not return anything.
  1518. */
  1519. enable() {
  1520. const { tabLabel, tabPane } = W.userscripts.registerSidebarTab("wazemyplaces");
  1521. tabLabel.innerHTML = "WazeMY Places";
  1522. tabLabel.title = "WazeMY Places";
  1523. tabPane.innerHTML = this.tabHTML;
  1524. // Populate select options with polygons from KVMR.
  1525. const map = W.map.getLayersBy("uniqueName", "__KlangValley");
  1526. map[0].features.forEach((feature) => {
  1527. $("#wazemyPlaces_polygons").append($("<option>", {
  1528. value: feature.data.number,
  1529. text: feature.data.number,
  1530. }));
  1531. });
  1532. // Handle Scan button.
  1533. $("#wazemyPlaces_scan").on("click", async () => {
  1534. $("#wazemyPlaces_scanStatus").text("Scanning tiles.");
  1535. $("#wazemyPlaces_venues > tbody").empty();
  1536. const map = W.map.getLayersBy("uniqueName", "__KlangValley");
  1537. if (map.length === 0) {
  1538. console.log("[PluginPlaces] No KVMR layer found. Aborting scan.");
  1539. return false;
  1540. }
  1541. const mr = map[0].getFeaturesByAttribute("number", $("#wazemyPlaces_polygons option:selected")[0].innerText);
  1542. if (mr.length === 0) {
  1543. console.log("[PluginPlaces] No polygon found. Aborting scan.");
  1544. return false;
  1545. }
  1546. const feature = mr[0];
  1547. let bounds = feature.geometry.getBounds().clone();
  1548. bounds = bounds.transform(W.map.getProjectionObject(), "EPSG:4326");
  1549. const venues = await getAllVenues(bounds);
  1550. let purCount = 0;
  1551. let totalCount = 0;
  1552. venues.forEach((venue) => {
  1553. // Check venue against rules.
  1554. const status = evaluateVenue(venue);
  1555. const isPUR = checkPURstatus(venue);
  1556. if (status.priority > 0 || isPUR) {
  1557. // Add venue to table.
  1558. let lon = 0;
  1559. let lat = 0;
  1560. if (venue.geometry.type === "Polygon") {
  1561. lon = venue.geometry.coordinates[0][0][0];
  1562. lat = venue.geometry.coordinates[0][0][1];
  1563. }
  1564. else {
  1565. lon = venue.geometry.coordinates[0];
  1566. lat = venue.geometry.coordinates[1];
  1567. }
  1568. const row = $("<tr>");
  1569. row.attr("id", `${lon}:${lat}:${venue.id}`);
  1570. row.on("click", (e) => {
  1571. const target = e.currentTarget.id.split(":"); // split to lon:lat:id
  1572. const xy = OpenLayers.Layer.SphericalMercator.forwardMercator(parseFloat(target[0]), parseFloat(target[1]));
  1573. W.map.setCenter(xy);
  1574. });
  1575. let purHTML = ``;
  1576. if (isPUR) {
  1577. purCount++;
  1578. if (venue.approved === false) {
  1579. purHTML = `<td align="center">N</td>`;
  1580. }
  1581. else if (venue.venueUpdateRequests[0].type === "REQUEST") {
  1582. if (venue.venueUpdateRequests[0].subType === "FLAG") {
  1583. purHTML = `<td align="center">F</td>`;
  1584. }
  1585. else if (venue.venueUpdateRequests[0].subType === "UPDATE") {
  1586. purHTML = `<td align="center">U</td>`;
  1587. }
  1588. else if (venue.venueUpdateRequests[0].subType === "DELETE") {
  1589. purHTML = `<td align="center">D</td>`;
  1590. }
  1591. else {
  1592. purHTML = `<td align="center">+</td>`;
  1593. }
  1594. }
  1595. else if (venue.venueUpdateRequests[0].type === "IMAGE") {
  1596. purHTML = `<td align="center">I</td>`;
  1597. }
  1598. else {
  1599. purHTML = `<td align="center">+</td>`;
  1600. }
  1601. }
  1602. else {
  1603. purHTML = `<td></td>`;
  1604. }
  1605. row.append(purHTML);
  1606. const levelHTML = `<td>${venue.lockRank ? venue.lockRank + 1 : 1}</td>`;
  1607. row.append(levelHTML);
  1608. const colHTML = `<td>${venue.name}</td>`;
  1609. row.append(colHTML);
  1610. const errorsHTML = `<td>${status.errors.join("\r\n")}</td>`;
  1611. row.append(errorsHTML);
  1612. $("#wazemyPlaces_venues > tbody").append(row);
  1613. totalCount++;
  1614. }
  1615. $("#wazemyPlaces_purCount").text(`# PUR = ${purCount}`);
  1616. $("#wazemyPlaces_totalCount").text(`# total = ${totalCount}`);
  1617. $("#wazemyPlaces_scanStatus").text("");
  1618. function evaluateVenue(venue) {
  1619. let status = {
  1620. priority: 0,
  1621. errors: [],
  1622. };
  1623. // Rule #1
  1624. if (typeof venue.name == "undefined") {
  1625. if (!venue.categories.includes("RESIDENCE_HOME")) {
  1626. status.priority = 3;
  1627. status.errors.push("Missing name.");
  1628. }
  1629. }
  1630. else {
  1631. // Rule: Check name for all uppercase.
  1632. if (venue.name === venue.name.toUpperCase()) {
  1633. status.priority = 3;
  1634. status.errors.push("Name is uppercase.");
  1635. }
  1636. // Rule: Check name for all lowercase.
  1637. if (venue.name === venue.name.toLowerCase()) {
  1638. status.priority = 3;
  1639. status.errors.push("Name is lowercase.");
  1640. }
  1641. }
  1642. // Rule: Min lock is not set.
  1643. if (venue.lockRank === 0) {
  1644. status.priority = 3;
  1645. status.errors.push("Min lock not set.");
  1646. }
  1647. // Rule: Phone number format.
  1648. if (venue.phone) {
  1649. if (/^[\d]{3}-[\d]{3} [\d]{4}$/.test(venue.phone) === false &&
  1650. /^[\d]{3}-[\d]{4} [\d]{4}$/.test(venue.phone) === false &&
  1651. /^[\d]{2}-[\d]{4} [\d]{4}$/.test(venue.phone) === false &&
  1652. /^[\d]{2}-[\d]{3} [\d]{4}$/.test(venue.phone) === false &&
  1653. /^[\d]{3}-[\d]{3} [\d]{3}$/.test(venue.phone) === false &&
  1654. /^[\d]{1}-[\d]{3}-[\d]{2}-[\d]{4}$/.test(venue.phone) === false) {
  1655. status.priority = 2;
  1656. status.errors.push("Phone number format incorrect.");
  1657. }
  1658. }
  1659. // Rule: Category specific rank locks.
  1660. if ((venue.categories.includes("CHARGING_STATION") &&
  1661. venue.lockRank < 3) ||
  1662. (venue.categories.includes("GAS_STATION") && venue.lockRank < 3) ||
  1663. (venue.categories.includes("AIRPORT") && venue.lockRank < 4) ||
  1664. (venue.categories.includes("BUS_STATION") && venue.lockRank < 2) ||
  1665. (venue.categories.includes("FERRY_PIER") && venue.lockRank < 2) ||
  1666. (venue.categories.includes("JUNCTION_INTERCHANGE") &&
  1667. venue.lockRank < 2) ||
  1668. (venue.categories.includes("REST_AREAS") && venue.lockRank < 2) ||
  1669. (venue.categories.includes("SEAPORT_MARINA_HARBOR") &&
  1670. venue.lockRank < 2) ||
  1671. (venue.categories.includes("TRAIN_STATION") &&
  1672. venue.lockRank < 2) ||
  1673. (venue.categories.includes("TUNNEL") && venue.lockRank < 2) ||
  1674. (venue.categories.includes("CITY_HALL") && venue.lockRank < 2) ||
  1675. (venue.categories.includes("COLLEGE_UNIVERSITY") &&
  1676. venue.lockRank < 2) ||
  1677. (venue.categories.includes("COURTHOUSE") && venue.lockRank < 2) ||
  1678. (venue.categories.includes("DOCTOR_CLINIC") &&
  1679. venue.lockRank < 2) ||
  1680. (venue.categories.includes("EMBASSY_CONSULATE") &&
  1681. venue.lockRank < 2) ||
  1682. (venue.categories.includes("FIRE_DEPARTMENT") &&
  1683. venue.lockRank < 2) ||
  1684. (venue.categories.includes("HOSPITAL_URGENT_CARE") &&
  1685. venue.lockRank < 3) ||
  1686. (venue.categories.includes("LIBRARY") && venue.lockRank < 2) ||
  1687. (venue.categories.includes("MILITARY") && venue.lockRank < 3) ||
  1688. (venue.categories.includes("POLICE_STATION") &&
  1689. venue.lockRank < 2) ||
  1690. (venue.categories.includes("PRISON_CORRECTIONAL_FACILITY") &&
  1691. venue.lockRank < 2) ||
  1692. (venue.categories.includes("RELIGIOUS_CENTER") &&
  1693. venue.lockRank < 3) ||
  1694. (venue.categories.includes("SCHOOL") && venue.lockRank < 2) ||
  1695. (venue.categories.includes("BANK_FINANCIAL") &&
  1696. venue.lockRank < 2) ||
  1697. (venue.categories.includes("SHOPPING_CENTER") &&
  1698. venue.lockRank < 2) ||
  1699. (venue.categories.includes("MUSEUM") && venue.lockRank < 2) ||
  1700. (venue.categories.includes("RACING_TRACK") && venue.lockRank < 2) ||
  1701. (venue.categories.includes("STADIUM_ARENA") &&
  1702. venue.lockRank < 2) ||
  1703. (venue.categories.includes("THEME_PARK") && venue.lockRank < 2) ||
  1704. (venue.categories.includes("TOURIST_ATTRACTION_HISTORIC_SITE") &&
  1705. venue.lockRank < 2) ||
  1706. (venue.categories.includes("ZOO_AQUARIUM") && venue.lockRank < 2) ||
  1707. (venue.categories.includes("BEACH") && venue.lockRank < 2) ||
  1708. (venue.categories.includes("GOLF_COURSE") && venue.lockRank < 2) ||
  1709. (venue.categories.includes("PARK") && venue.lockRank < 2) ||
  1710. (venue.categories.includes("FOREST_GROVE") && venue.lockRank < 2) ||
  1711. (venue.categories.includes("ISLAND") && venue.lockRank < 4) ||
  1712. (venue.categories.includes("RIVER_STREAM") && venue.lockRank < 3) ||
  1713. (venue.categories.includes("SEA_LAKE_POOL") &&
  1714. venue.lockRank < 5) ||
  1715. (venue.categories.includes("CANAL") && venue.lockRank < 2) ||
  1716. (venue.categories.includes("SWAMP_MARSH") && venue.lockRank < 2)) {
  1717. status.priority = 2;
  1718. status.errors.push("Min lock incorrect.");
  1719. }
  1720. return status;
  1721. }
  1722. function checkPURstatus(venue) {
  1723. if (venue.venueUpdateRequests?.length > 0) {
  1724. return true;
  1725. }
  1726. else {
  1727. return false;
  1728. }
  1729. }
  1730. });
  1731. async function getAllVenues(bounds) {
  1732. let venues = [];
  1733. // console.log(bounds);
  1734. const baseURL = "https://www.waze.com/row-Descartes/app/Features?language=en&v=2&cameras=true&mapComments=true&roadClosures=true&roadTypes=1%2C2%2C3%2C4%2C5%2C6%2C7%2C8%2C9%2C10%2C15%2C16%2C17%2C18%2C19%2C20%2C22&venueLevel=4&venueFilter=1%2C1%2C1%2C1&";
  1735. let urls = [];
  1736. const stepSize = 0.1;
  1737. for (let left = bounds.left; left <= bounds.right; left += stepSize) {
  1738. for (let bottom = bounds.bottom; bottom <= bounds.top; bottom += stepSize) {
  1739. urls.push(`bbox=${left}%2C${bottom}%2C${left + stepSize > bounds.right ? bounds.right : left + stepSize}%2C${bottom + stepSize > bounds.top ? bounds.top : bottom + stepSize}`);
  1740. }
  1741. }
  1742. for (let i = 0; i < urls.length; i++) {
  1743. // console.log(baseURL + urls[i]);
  1744. $("#wazemyPlaces_scanStatus").text(`Scanning tile ${i + 1} of ${urls.length}.`);
  1745. const result = await GM.xmlHttpRequest({
  1746. method: "GET",
  1747. responseType: "json",
  1748. url: baseURL + urls[i],
  1749. }).catch((e) => console.error(e));
  1750. venues = venues.concat(result.response.venues.objects);
  1751. }
  1752. return venues;
  1753. }
  1754. });
  1755. console.log("[WazeMY] PluginPlaces enabled.");
  1756. }
  1757. /**
  1758. * Disable plugin.
  1759. *
  1760. * @return {void} This function does not return anything.
  1761. */
  1762. disable() {
  1763. if ($("span[title='WazeMY Places']").length > 0) {
  1764. W.userscripts.removeSidebarTab("wazemyplaces");
  1765. }
  1766. console.log("[WazeMY] PluginPlaces disabled.");
  1767. }
  1768. /**
  1769. * Updates the settings of the PluginPlaces based on the provided settings object.
  1770. *
  1771. * @return {void} This function does not return anything.
  1772. */
  1773. updateSettings(settings) {
  1774. if (settings.enable === true) {
  1775. this.enable();
  1776. }
  1777. else {
  1778. this.disable();
  1779. }
  1780. console.log("[WazeMY] PluginPlaces settings updated.", settings);
  1781. }
  1782. }
  1783.  
  1784. ;// ./src/plugins/PluginGemini.ts
  1785.  
  1786.  
  1787. class PluginGemini {
  1788. /**
  1789. * Constructs a new instance of the PluginGemini class.
  1790. * Initializes the WME SDK with the specified script ID and name,
  1791. * and calls the initialize method to set up the plugin.
  1792. */
  1793. constructor() {
  1794. this.sdk = unsafeWindow.getWmeSdk({
  1795. scriptId: "wme-wazemy-gemini",
  1796. scriptName: "WazeMY",
  1797. });
  1798. this.initialize();
  1799. }
  1800. /**
  1801. * Initialize plugin.
  1802. *
  1803. * @return {void} This function does not return anything.
  1804. */
  1805. initialize() {
  1806. const settingsHTML = `
  1807. <div>
  1808. <input type="checkbox" id="wazemySettings_gemini_enable"/>
  1809. <label for="wazemySettings_gemini_enable">Enable Gemini integration</label>
  1810. </div>
  1811. `;
  1812. $("#wazemySettings_settings").append(settingsHTML);
  1813. $("#wazemySettings_gemini_enable").on("change", () => {
  1814. PluginManager.instance.updatePluginSettings("gemini", {
  1815. enable: $("#wazemySettings_gemini_enable").prop("checked"),
  1816. });
  1817. });
  1818. const geminiAPIKeySettings = `
  1819. <div>
  1820. <label for="wazemySettings_gemini_apiKey">API Key</label>
  1821. <input type="password" id="wazemySettings_gemini_apiKey" placeholder="Enter Gemini API Key"/>
  1822. <wz-button id="wazemySettings_gemini_saveApiKey" class="wazemySettingsButton" style="padding:3px">
  1823. Save
  1824. </wz-button>
  1825. <div style="font-size: 10px; margin-top: 5px;">
  1826. Get your API key from <a href="https://aistudio.google.com/" target="_blank">Google AI Studio</a>.
  1827. </div>
  1828. </div>
  1829. `;
  1830. $("#wazemySettings_gemini").append(geminiAPIKeySettings);
  1831. $("#wazemySettings_gemini_saveApiKey").on("click", () => {
  1832. PluginManager.instance.updatePluginSettings("gemini", {
  1833. geminiApiKey: $("#wazemySettings_gemini_apiKey").val(),
  1834. });
  1835. });
  1836. // Set settings according to last stored value.
  1837. const savedSettings = SettingsStorage.instance.getSetting("gemini");
  1838. if (savedSettings?.enable === true) {
  1839. $("#wazemySettings_gemini_enable").prop("checked", true);
  1840. }
  1841. else {
  1842. $("#wazemySettings_gemini_enable").prop("checked", false);
  1843. }
  1844. if (savedSettings?.geminiApiKey) {
  1845. $("#wazemySettings_gemini_apiKey").val(savedSettings.geminiApiKey);
  1846. this.geminiApiKey = savedSettings.geminiApiKey;
  1847. }
  1848. // let aiAnswer = this.getGeminiTextResponse("How AI does work?");
  1849. // console.log("[WazeMY] Gemini AI Answer:", aiAnswer);
  1850. this.initializeVenueUpdateRequestImageHelper();
  1851. console.log("[WazeMY] PluginGemini initialized.");
  1852. }
  1853. /**
  1854. * Enable plugin.
  1855. *
  1856. * @return {void} This function does not return anything.
  1857. */
  1858. enable() {
  1859. console.log("[WazeMY] PluginGemini enabled.");
  1860. }
  1861. /**
  1862. * Disable plugin.
  1863. *
  1864. * @return {void} This function does not return anything.
  1865. */
  1866. disable() {
  1867. console.log("[WazeMY] PluginGemini disabled.");
  1868. }
  1869. /**
  1870. * Updates the settings of the PluginGemini based on the provided settings object.
  1871. *
  1872. * @return {void} This function does not return anything.
  1873. */
  1874. updateSettings(settings) {
  1875. if (settings.enable) {
  1876. if (settings.enable === true) {
  1877. this.enable();
  1878. }
  1879. else {
  1880. this.disable();
  1881. }
  1882. }
  1883. console.log("[WazeMY] PluginGemini settings updated.");
  1884. }
  1885. /**
  1886. * Initialize the helper for venue update request image.
  1887. *
  1888. * @return {void} This function does not return anything.
  1889. */
  1890. initializeVenueUpdateRequestImageHelper() {
  1891. const onload_base64image = (response) => {
  1892. const base64data = btoa(response.responseText);
  1893. this.getGeminiPictureEvaluation(base64data).then((evaluation) => {
  1894. const jsonMatch = evaluation.match(/```json\n([\s\S]*?)\n```/);
  1895. let evaluationText;
  1896. if (jsonMatch && jsonMatch[1]) {
  1897. evaluationText = JSON.parse(jsonMatch[1]);
  1898. }
  1899. else {
  1900. evaluationText = JSON.parse(evaluation);
  1901. }
  1902. $("#gemini").replaceWith(`<div id='gemini' class="changes"><b>Gemini image evaluation: ${evaluationText.suggestion}</b><br><i>${evaluationText.reason}</i><br></div>`);
  1903. if (evaluationText.suggestion === "Reject") {
  1904. $("#gemini").append(`<b>Violations:</b><ul>${evaluationText.violations.map((v) => `<li>${v}</li>`).join("")}</ul>`);
  1905. }
  1906. });
  1907. };
  1908. const evaluateImage = (displayAfterElement) => {
  1909. const imagePreview = $(".image-preview");
  1910. const geminiElement = $("#gemini");
  1911. if (imagePreview.length > 0 && geminiElement.length === 0) {
  1912. $(displayAfterElement).after("<div id='gemini' class='changes'><i>Gemini is evaluating...</i><br></div>");
  1913. if (imagePreview.length > 0) {
  1914. const src = imagePreview.attr("src");
  1915. GM_xmlhttpRequest({
  1916. method: "GET",
  1917. url: src,
  1918. responseType: "arraybuffer",
  1919. onload: onload_base64image.bind(this),
  1920. });
  1921. }
  1922. }
  1923. };
  1924. $(document.body).on("click", "", (_e) => {
  1925. evaluateImage("div.changes");
  1926. });
  1927. }
  1928. getGeminiTextResponse(context) {
  1929. return new Promise((resolve, reject) => {
  1930. if (!this.geminiApiKey) {
  1931. reject(new Error("Gemini API key is not set."));
  1932. return;
  1933. }
  1934. const model = "gemini-2.5-flash";
  1935. const message = {
  1936. contents: [{ parts: [{ text: context }] }],
  1937. generationConfig: { thinkingConfig: { thinkingBudget: 0 } },
  1938. };
  1939. GM_xmlhttpRequest({
  1940. method: "POST",
  1941. url: `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${this.geminiApiKey}`,
  1942. headers: { "Content-Type": "application/json" },
  1943. data: JSON.stringify(message),
  1944. onload: function (response) {
  1945. try {
  1946. const data = JSON.parse(response.responseText);
  1947. // console.log(data["candidates"][0]["content"]["parts"][0]["text"]);
  1948. resolve(data["candidates"][0]["content"]["parts"][0]["text"]);
  1949. }
  1950. catch (e) {
  1951. console.error(new Error("Failed to parse response: " + e.message));
  1952. reject(new Error(`Failed to parse Gemini response: ${e.message}`));
  1953. }
  1954. },
  1955. });
  1956. });
  1957. }
  1958. getGeminiPictureEvaluation(base64ImageData) {
  1959. return new Promise((resolve, reject) => {
  1960. if (!this.geminiApiKey) {
  1961. reject(new Error("Gemini API key is not set."));
  1962. return;
  1963. }
  1964. const model = "gemini-2.5-flash";
  1965. const prompt = `You are an AI assistant specialized in Waze Map Editor (WME) venue image moderation.
  1966.  
  1967. Your task is to evaluate an uploaded image of a Waze venue against Waze Map Editor's official guidelines for venue images. You will determine if the image should be 'Approved' or 'Rejected' and provide a clear, concise justification, along with specific guideline violations if rejected.
  1968.  
  1969. I will provide you with an image for evaluation.
  1970.  
  1971. Your output MUST be a JSON object, easily parseable by a Tampermonkey script. Do not include any other text or formatting outside of the JSON.
  1972.  
  1973. \`\`\`json
  1974. {
  1975. "suggestion": "Approve" | "Reject",
  1976. "reason": "Concise explanation for the decision (e.g., 'Image is clear, relevant, and follows all guidelines.' or 'Image is rejected due to blurriness and irrelevance.').",
  1977. "violations": [
  1978. // Array of specific guideline violation codes if decision is "Rejected".
  1979. // Use the following codes:
  1980. // "IRRELEVANT_IMAGE": Image does not show the venue or is of a random object.
  1981. // "LOW_QUALITY": Image is blurry, dark, pixelated, overexposed, or distorted.
  1982. // "INAPPROPRIATE_CONTENT": Contains nudity, violence, hate speech, etc.
  1983. // "PERSONAL_INFORMATION": Shows identifiable faces without consent, license plates, private addresses not part of the venue.
  1984. // "SCREENSHOT_OF_MAP": Image is a screenshot of Waze, Google Maps, or any other mapping application.
  1985. // "EXCESSIVE_TEXT_OR_OVERLAYS": Image has too much text, watermarks, promotional overlays, or graphic elements not inherent to the venue's physical signage.
  1986. // "COPYRIGHTED_MATERIAL": Image appears to be copyrighted without proper authorization (use with caution, AI inference only).
  1987. // "ORIENTATION_OR_CROPPING_ISSUE": Image is badly rotated or cropped, making the venue unclear.
  1988. // "DUPLICATE_IMAGE": Image is a clear duplicate of an existing venue image (requires contextual awareness, AI may struggle here).
  1989. // "OTHER_GENERAL_ISSUE": Any other issues not covered by specific codes.
  1990. ]
  1991. }
  1992. \`\`\`
  1993.  
  1994. Waze Venue Image Guidelines to Consider:
  1995.  
  1996. 1. **Relevance:** The image *must* clearly depict the venue/business itself (e.g., its entrance, sign, storefront). No random objects, people (unless part of a large crowd at an event, but generally avoided), or irrelevant scenery.
  1997. 2. **Quality:** Images must be clear, well-lit, in focus, and not blurry, pixelated, overexposed, or excessively dark.
  1998. 3. **Appropriateness:** No offensive, violent, sexually explicit, or hateful content.
  1999. 4. **No Personal Information:** Avoid identifiable faces (especially children), license plates, or other sensitive personal data unless it's an unchangeable part of the venue's permanent signage.
  2000. 5. **No Map Screenshots:** Do not approve images that are screenshots of Waze, Google Maps, or any other navigation/map application.
  2001. 6. **Minimal Text/Overlays:** Avoid images with excessive text, watermarks, promotional overlays, or graphic elements that are not part of the venue's physical branding/signage. A clear logo on a sign is generally fine; a flyer overlay is not.
  2002. 7. **Copyright:** Avoid copyrighted images without explicit permission (AI should err on the side of caution).
  2003. 8. **Focus:** The primary subject of the image should be the venue.
  2004. 9. **Orientation:** Landscape orientation is generally preferred for display, but a good quality portrait image of a tall building is acceptable if it clearly shows the venue. Poor rotation is a rejection reason.
  2005.  
  2006. Decision Logic:
  2007.  
  2008. * If the image adheres to all the above guidelines, set 'decision' to "Approved".
  2009. * If the image violates one or more guidelines, set 'decision' to "Rejected" and list *all* applicable violation codes in the 'violations' array.`;
  2010. const message = {
  2011. contents: [
  2012. {
  2013. parts: [
  2014. {
  2015. inlineData: {
  2016. mimeType: "image/jpeg",
  2017. data: base64ImageData,
  2018. },
  2019. },
  2020. {
  2021. text: prompt,
  2022. },
  2023. ],
  2024. },
  2025. ],
  2026. };
  2027. GM_xmlhttpRequest({
  2028. method: "POST",
  2029. url: `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${this.geminiApiKey}`,
  2030. headers: { "Content-Type": "application/json" },
  2031. data: JSON.stringify(message),
  2032. onload: function (response) {
  2033. try {
  2034. const data = JSON.parse(response.responseText);
  2035. // console.log(data["candidates"][0]["content"]["parts"][0]["text"]);
  2036. resolve(data["candidates"][0]["content"]["parts"][0]["text"]);
  2037. }
  2038. catch (e) {
  2039. console.log(new Error("Failed to parse response: " + e.message));
  2040. reject(new Error(`Failed to parse Gemini response: ${e.message}`));
  2041. }
  2042. },
  2043. });
  2044. });
  2045. }
  2046. }
  2047.  
  2048. ;// ./src/PluginFactory.ts
  2049.  
  2050.  
  2051.  
  2052.  
  2053.  
  2054.  
  2055.  
  2056. class PluginFactory {
  2057. static createPlugin(pluginName) {
  2058. switch (pluginName) {
  2059. case "PluginTooltip":
  2060. return new PluginTooltip();
  2061. case "PluginCopyLatLon":
  2062. return new PluginCopyLatLon();
  2063. case "PluginTrafficCameras":
  2064. return new PluginTrafficCameras();
  2065. case "PluginKVMR":
  2066. return new PluginKVMR();
  2067. case "PluginZoomPic":
  2068. return new PluginZoomPic();
  2069. case "PluginPlaces":
  2070. return new PluginPlaces();
  2071. case "PluginGemini":
  2072. return new PluginGemini();
  2073. default:
  2074. throw new Error(`Unknown plugin: ${pluginName}`);
  2075. }
  2076. }
  2077. }
  2078.  
  2079. ;// ./src/PluginManager.ts
  2080.  
  2081.  
  2082. class PluginManager {
  2083. constructor(settings) {
  2084. this.plugins = {};
  2085. this.settingsStorage = settings;
  2086. }
  2087. /**
  2088. * Adds a plugin to the PluginManager.
  2089. *
  2090. * @param {string} key - The key to associate the plugin with.
  2091. * @param {string} type - The type of plugin to create.
  2092. * @return {void} This function does not return anything.
  2093. */
  2094. addPlugin(key, type) {
  2095. const plugin = PluginFactory.createPlugin(type);
  2096. this.plugins[key] = plugin;
  2097. const pluginSettings = this.settingsStorage.getSetting(key);
  2098. if (pluginSettings) {
  2099. plugin.updateSettings(pluginSettings);
  2100. }
  2101. }
  2102. /**
  2103. * Removes a plugin from the PluginManager.
  2104. *
  2105. * @param {string} key - The key associated with the plugin to remove.
  2106. * @return {void} This function does not return anything.
  2107. */
  2108. removePlugin(key) {
  2109. if (this.plugins[key]) {
  2110. this.settingsStorage.removeSetting(key);
  2111. delete this.plugins[key];
  2112. }
  2113. }
  2114. /**
  2115. * Enables a plugin with the given key if it exists.
  2116. *
  2117. * @param {string} key - The key of the plugin to enable.
  2118. * @return {void} This function does not return anything.
  2119. */
  2120. enablePlugin(key) {
  2121. if (this.plugins[key]) {
  2122. this.plugins[key].enable();
  2123. }
  2124. }
  2125. /**
  2126. * Disables a plugin with the given key if it exists.
  2127. *
  2128. * @param {string} key - The key of the plugin to disable.
  2129. * @return {void} This function does not return anything.
  2130. */
  2131. disablePlugin(key) {
  2132. if (this.plugins[key]) {
  2133. this.plugins[key].disable();
  2134. }
  2135. }
  2136. /**
  2137. * Updates the settings of a plugin associated with the given key.
  2138. *
  2139. * @param {string} key - The key associated with the plugin.
  2140. * @param {any} settings - The new settings to be applied to the plugin.
  2141. * @return {void} This function does not return anything.
  2142. */
  2143. updatePluginSettings(key, settings) {
  2144. if (this.plugins[key]) {
  2145. this.plugins[key].updateSettings(settings);
  2146. this.settingsStorage.updateSetting(key, settings);
  2147. }
  2148. }
  2149. }
  2150. PluginManager.instance = new PluginManager(SettingsStorage.instance);
  2151.  
  2152. ;// ./src/index.ts
  2153.  
  2154.  
  2155. const updateMessage = `Port script to WME SDK and Gemini integration.`;
  2156. var sdk;
  2157. console.log("[WazeMY] Script started");
  2158. unsafeWindow.SDK_INITIALIZED.then(initScript);
  2159. function initScript() {
  2160. if (!unsafeWindow.getWmeSdk) {
  2161. throw new Error("WME SDK not available");
  2162. }
  2163. sdk = unsafeWindow.getWmeSdk({
  2164. scriptId: "wme-wazemy",
  2165. scriptName: "WazeMY",
  2166. });
  2167. sdk.Events.once({ eventName: "wme-ready" }).then(initializeWazeMY);
  2168. }
  2169. function initializeWazeMY() {
  2170. console.log("[WazeMY] WME ready");
  2171. sdk.Sidebar.registerScriptTab().then((sidebarResult) => {
  2172. sidebarResult.tabLabel.innerHTML = "WazeMY";
  2173. sidebarResult.tabLabel.title = "WazeMY";
  2174. sidebarResult.tabPane.innerHTML = `
  2175. <wz-section-header headline="WazeMY" size="section-header2" class="settings-header">
  2176. <wz-overline class="headline">WazeMY</wz-overline>
  2177. </wz-section-header>
  2178. <wz-overline class="headline">${GM_info.script.version}</wz-overline>
  2179. <div class="settings">
  2180. <div class="settings__form-group">
  2181. <fieldset class="wazemySettings">
  2182. <legend class="wazemySettingsLegend">
  2183. <wz-label>Settings</wz-label>
  2184. </legend>
  2185. <div id="wazemySettings_settings"></div>
  2186. </fieldset>
  2187. </div>
  2188. <div class="settings__form-group">
  2189. <fieldset class="wazemySettings">
  2190. <legend class="wazemySettingsLegend">
  2191. <wz-label>Shortcuts</wz-label>
  2192. </legend>
  2193. <div id="wazemySettings_shortcuts"></div>
  2194. </fieldset>
  2195. </div>
  2196. <div class="settings__form-group">
  2197. <fieldset class="wazemySettings">
  2198. <legend class="wazemySettingsLegend">
  2199. <wz-label>Gemini</wz-label>
  2200. </legend>
  2201. <div id="wazemySettings_gemini"></div>
  2202. </fieldset>
  2203. </div>
  2204. </div>
  2205. `;
  2206. WazeWrap.Interface.ShowScriptUpdate("WME WazeMY", GM_info.script.version, updateMessage, "https://greasyfork.org/en/scripts/404584-wazemy", "javascript:alert('No forum available');");
  2207. const pluginManager = PluginManager.instance;
  2208. pluginManager.addPlugin("copylatlon", "PluginCopyLatLon");
  2209. pluginManager.addPlugin("tooltip", "PluginTooltip");
  2210. pluginManager.addPlugin("trafcam", "PluginTrafficCameras");
  2211. pluginManager.addPlugin("kvmr", "PluginKVMR");
  2212. pluginManager.addPlugin("zoompic", "PluginZoomPic");
  2213. pluginManager.addPlugin("places", "PluginPlaces");
  2214. pluginManager.addPlugin("gemini", "PluginGemini");
  2215. });
  2216. }
  2217.  
  2218. /******/ })()
  2219. ;