// ==UserScript==
// @name WME ClickSaver
// @namespace https://gf.qytechs.cn/users/45389
// @version 2022.08.18.001
// @description Various UI changes to make editing faster and easier.
// @author MapOMatic
// @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/
// @license GNU GPLv3
// @connect sheets.googleapis.com
// @contributionURL https://github.com/WazeDev/Thank-The-Authors
// @grant GM_xmlhttpRequest
// @require https://gf.qytechs.cn/scripts/24851-wazewrap/code/WazeWrap.js
// ==/UserScript==
/* global GM_info */
/* global W */
/* global Node */
/* global I18n */
/* global OL */
/* global $ */
/* global document */
/* global MutationObserver */
/* global localStorage */
/* global confirm */
/* global atob */
/* global WazeWrap */
/* global window */
const UPDATE_MESSAGE = '';
const SCRIPT_NAME = GM_info.script.name;
const SCRIPT_VERSION = GM_info.script.version;
const FORUM_URL = 'https://www.waze.com/forum/viewtopic.php?f=819&t=199894';
const TRANSLATIONS_URL = 'https://sheets.googleapis.com/v4/spreadsheets/1ZlE9yhNncP9iZrPzFFa-FCtYuK58wNOEcmKqng4sH1M/values/ClickSaver';
const API_KEY = 'YTJWNVBVRkplbUZUZVVGMFl6aFVjMjVOTW0wNU5GaG5kVE40TUZoNWJVZEhWbU5rUjNacVdtdFlWUT09';
const DEC = s => atob(atob(s));
const EXTERNAL_SETTINGS = {
toggleTwoWaySegDrawingShortcut: null
};
const EXTERNAL_SETTINGS_NAME = 'clicksaver_settings_ext';
// This function is injected into the page.
function main(argsObject) {
/* eslint-disable object-curly-newline */
const ROAD_TYPE_DROPDOWN_SELECTOR = 'wz-select[name="roadType"]';
const ELEVATION_DROPDOWN_SELECTOR = '.side-panel-section wz-select[name="level"]';
const ROUTING_TYPE_DROPDOWN_SELECTOR = 'wz-select[name="routingRoadType"]';
const PARKING_SPACES_DROPDOWN_SELECTOR = 'select[name="estimatedNumberOfSpots"]';
const PARKING_COST_DROPDOWN_SELECTOR = 'select[name="costType"]';
const SETTINGS_STORE_NAME = 'clicksaver_settings';
const DEFAULT_TRANSLATION = {
roadTypeButtons: {
St: { text: 'St' },
PS: { text: 'PS' },
mH: { text: 'mH' },
MH: { text: 'MH' },
Fw: { text: 'Fw' },
Rmp: { text: 'Rmp' },
OR: { text: 'OR' },
PLR: { text: 'PLR' },
PR: { text: 'PR' },
Fer: { text: 'Fer' },
WT: { text: 'WT' },
PB: { text: 'PB' },
Sw: { text: 'Sw' },
RR: { text: 'RR' },
RT: { text: 'RT' },
Pw: { text: 'Pw' }
},
directionButtons: {
twoWay: { title: 'Two way', text: 'Two way' },
oneWayAB: { title: 'One way (A → B)', text: 'A → B' },
oneWayBA: { title: 'One way (B → A)', text: 'B → A' },
unknown: { title: 'Unknown', text: '?' }
},
groundButtonText: 'Ground',
autoLockButtonText: 'Auto',
multiLockLevelWarning: 'Multiple lock levels selected!',
prefs: {
dropdownHelperGroup: 'DROPDOWN HELPERS',
roadTypeButtons: 'Add road type buttons',
useOldRoadColors: 'Use old road colors (requires refresh)',
setStreetCityToNone: 'Set Street/City to None (new seg\'s only)',
setStreetCityToNone_Title: 'NOTE: Only works if connected directly or indirectly'
+ ' to a segment with State / Country already set.',
setCityToConnectedSegCity: 'Set City to connected segment\'s City',
routingTypeButtons: 'Add routing type buttons',
elevationButtons: 'Add elevation buttons',
parkingCostButtons: 'Add PLA cost buttons',
parkingSpacesButtons: 'Add PLA estimated spaces buttons',
spaceSaversGroup: 'SPACE SAVERS',
inlineRoadType: 'Inline road type checkboxes',
avgSpeedCameras: 'Hide Avg Speed Cameras',
inlineParkingStuff: 'Inline parking/payment type checkboxes',
discussionForumLinkText: 'Discussion Forum'
}
};
const ROAD_TYPES = {
St: { val: 1, wmeColor: '#ffffeb', svColor: '#ffffff', category: 'streets', visible: true },
PS: { val: 2, wmeColor: '#f0ea58', svColor: '#cba12e', category: 'streets', visible: true },
Pw: { val: 22, wmeColor: '#64799a', svColor: '#64799a', category: 'streets', visible: false },
mH: { val: 7, wmeColor: '#69bf88', svColor: '#ece589', category: 'highways', visible: true },
MH: { val: 6, wmeColor: '#45b8d1', svColor: '#c13040', category: 'highways', visible: true },
Fw: { val: 3, wmeColor: '#c577d2', svColor: '#387fb8', category: 'highways', visible: false },
Rmp: { val: 4, wmeColor: '#b3bfb3', svColor: '#58c53b', category: 'highways', visible: false },
OR: { val: 8, wmeColor: '#867342', svColor: '#82614a', category: 'otherDrivable', visible: false },
PLR: { val: 20, wmeColor: '#ababab', svColor: '#2282ab', category: 'otherDrivable', visible: true },
PR: { val: 17, wmeColor: '#beba6c', svColor: '#00ffb3', category: 'otherDrivable', visible: true },
Fer: { val: 15, wmeColor: '#d7d8f8', svColor: '#ff8000', category: 'otherDrivable', visible: false },
RR: { val: 18, wmeColor: '#c62925', svColor: '#ffffff', category: 'nonDrivable', visible: false },
RT: { val: 19, wmeColor: '#ffffff', svColor: '#00ff00', category: 'nonDrivable', visible: false },
WT: { val: 5, wmeColor: '#b0a790', svColor: '#00ff00', category: 'pedestrian', visible: false },
PB: { val: 10, wmeColor: '#9a9a9a', svColor: '#0000ff', category: 'pedestrian', visible: false },
Sw: { val: 16, wmeColor: '#999999', svColor: '#b700ff', category: 'pedestrian', visible: false }
};
const DIRECTIONS = { twoWay: { val: 3 }, oneWayAB: { val: 1 }, oneWayBA: { val: 2 }, unknown: { val: 0 } };
/* eslint-enable object-curly-newline */
let _settings = {};
let _trans; // Translation object
// Do not make these const values. They may get assigned before require() is defined. Trust me. Don't do it.
let UpdateObject;
let UpdateFeatureAddress;
let MultiAction;
let AddSeg;
let Segment;
let DelSeg;
// function log(message) {
// console.log('ClickSaver:', message);
// }
function logDebug(message) {
console.debug('ClickSaver:', message);
}
// function logWarning(message) {
// console.warn('ClickSaver:', message);
// }
// function logError(message) {
// console.error('ClickSaver:', message);
// }
function isChecked(checkboxId) {
return $(`#${checkboxId}`).is(':checked');
}
function isSwapPedestrianPermitted() {
const { user } = W.loginManager;
const rank = user.rank + 1;
return rank >= 4 || (rank === 3 && user.isAreaManager);
}
function setChecked(checkboxId, checked) {
$(`#${checkboxId}`).prop('checked', checked);
}
function loadSettingsFromStorage() {
const loadedSettings = $.parseJSON(localStorage.getItem(SETTINGS_STORE_NAME));
const defaultSettings = {
lastVersion: null,
roadButtons: true,
roadTypeButtons: ['St', 'PS', 'mH', 'MH', 'Fw', 'Rmp', 'PLR', 'PR', 'PB'],
lockButtons: true,
elevationButtons: true,
directionButtons: true,
routingTypeButtons: true,
parkingCostButtons: true,
parkingSpacesButtons: true,
setNewPLRStreetToNone: true,
setNewPLRCity: true,
setNewPRStreetToNone: false,
setNewPRCity: false,
setNewRRStreetToNone: true, // added by jm6087
setNewRRCity: false, // added by jm6087
setNewPBStreetToNone: true, // added by jm6087
setNewPBCity: true, // added by jm6087
addAltCityButton: true,
addSwapPedestrianButton: false,
useOldRoadColors: false,
warnOnPedestrianTypeSwap: true
};
_settings = loadedSettings || defaultSettings;
Object.keys(defaultSettings).forEach(prop => {
if (!_settings.hasOwnProperty(prop)) {
_settings[prop] = defaultSettings[prop];
}
});
setChecked('csRoadTypeButtonsCheckBox', _settings.roadButtons);
if (_settings.roadTypeButtons) {
Object.keys(ROAD_TYPES).forEach(roadTypeAbbr1 => {
setChecked(`cs${roadTypeAbbr1}CheckBox`, _settings.roadTypeButtons.indexOf(roadTypeAbbr1) !== -1);
});
}
if (_settings.roadButtons) {
$('.csRoadTypeButtonsCheckBoxContainer').show();
} else {
$('.csRoadTypeButtonsCheckBoxContainer').hide();
}
setChecked('csLockButtonsCheckBox', _settings.lockButtons);
setChecked('csElevationButtonsCheckBox', _settings.elevationButtons);
setChecked('csDirectionButtonsCheckBox', _settings.directionButtons);
setChecked('csParkingSpacesButtonsCheckBox', _settings.parkingSpacesButtons);
setChecked('csParkingCostButtonsCheckBox', _settings.parkingCostButtons);
setChecked('csRoutingTypeCheckBox', _settings.routingTypeButtons);
setChecked('csClearNewPLRCheckBox', _settings.setNewPLRStreetToNone);
setChecked('csClearNewPRCheckBox', _settings.setNewPRStreetToNone);
setChecked('csClearNewRRCheckBox', _settings.setNewRRStreetToNone); // added by jm6087
setChecked('csClearNewPBCheckBox', _settings.setNewPBStreetToNone); // added by jm6087
setChecked('csUseOldRoadColorsCheckBox', _settings.useOldRoadColors);
setChecked('csSetNewPLRCityCheckBox', _settings.setNewPLRCity);
setChecked('csSetNewPRCityCheckBox', _settings.setNewPRCity);
setChecked('csAddAltCityButtonCheckBox', _settings.addAltCityButton);
setChecked('csAddSwapPedestrianButtonCheckBox', _settings.addSwapPedestrianButton);
}
function saveSettingsToStorage() {
if (localStorage) {
const settings = {
lastVersion: argsObject.scriptVersion,
roadButtons: _settings.roadButtons,
lockButtons: _settings.lockButtons,
elevationButtons: _settings.elevationButtons,
directionButtons: _settings.directionButtons,
parkingCostButtons: _settings.parkingCostButtons,
parkingSpacesButtons: _settings.parkingSpacesButtons,
setNewPLRStreetToNone: _settings.setNewPLRStreetToNone,
setNewPRStreetToNone: _settings.setNewPRStreetToNone,
setNewRRStreetToNone: _settings.setNewRRStreetToNone, // added by jm6087
setNewPBStreetToNone: _settings.setNewPBStreetToNone, // added by jm6087
routingTypeButtons: _settings.routingTypeButtons,
useOldRoadColors: _settings.useOldRoadColors,
setNewPLRCity: _settings.setNewPLRCity,
setNewPRCity: _settings.setNewPRCity,
addAltCityButton: _settings.addAltCityButton,
addSwapPedestrianButton: _settings.addSwapPedestrianButton,
warnOnPedestrianTypeSwap: _settings.warnOnPedestrianTypeSwap
};
settings.roadTypeButtons = [];
Object.keys(ROAD_TYPES).forEach(roadTypeAbbr => {
if (_settings.roadTypeButtons.indexOf(roadTypeAbbr) !== -1) {
settings.roadTypeButtons.push(roadTypeAbbr);
}
});
localStorage.setItem(SETTINGS_STORE_NAME, JSON.stringify(settings));
logDebug('Settings saved');
}
}
function isPedestrianTypeSegment(segment) {
return [5, 10, 16].indexOf(segment.attributes.roadType) > -1;
}
function getConnectedSegmentIDs(segmentID) {
const IDs = [];
const segment = W.model.segments.getObjectById(segmentID);
[
W.model.nodes.getObjectById(segment.attributes.fromNodeID),
W.model.nodes.getObjectById(segment.attributes.toNodeID)
].forEach(node => {
if (node) {
node.attributes.segIDs.forEach(segID => {
if (segID !== segmentID) { IDs.push(segID); }
});
}
});
return IDs;
}
function getFirstConnectedSegmentAddress(startSegment) {
const nonMatches = [];
const segmentIDsToSearch = [startSegment.getID()];
while (segmentIDsToSearch.length > 0) {
const startSegmentID = segmentIDsToSearch.pop();
startSegment = W.model.segments.getObjectById(startSegmentID);
const connectedSegmentIDs = getConnectedSegmentIDs(startSegmentID);
for (let i = 0; i < connectedSegmentIDs.length; i++) {
const addr = W.model.segments.getObjectById(connectedSegmentIDs[i]).getAddress();
if (!addr.isEmpty()) {
return addr;
}
}
nonMatches.push(startSegmentID);
connectedSegmentIDs.forEach(segmentID => {
if (nonMatches.indexOf(segmentID) === -1 && segmentIDsToSearch.indexOf(segmentID) === -1) {
segmentIDsToSearch.push(segmentID);
}
});
}
return undefined;
}
function setStreetAndCity(setCity) {
const segments = W.selectionManager.getSelectedFeatures();
if (segments.length === 0 || segments[0].model.type !== 'segment') {
return;
}
const mAction = new MultiAction();
mAction.setModel(W.model);
segments.forEach(segment => {
const segModel = segment.model;
if (segModel.attributes.primaryStreetID === null) {
const addr = getFirstConnectedSegmentAddress(segModel);
if (addr && !addr.isEmpty()) {
const cityNameToSet = setCity && !addr.getCity().isEmpty() ? addr.getCityName() : '';
const action = new UpdateFeatureAddress(segModel, {
countryID: addr.getCountry().id,
stateID: addr.getState().id,
cityName: cityNameToSet,
emptyStreet: true,
emptyCity: !setCity
}, { streetIDField: 'primaryStreetID' });
mAction.doSubAction(action);
}
}
});
const count = mAction.subActions.length;
if (count) {
mAction._description = `Updated address on ${count} segment${count > 1 ? 's' : ''}`;
W.model.actionManager.add(mAction);
}
}
// eslint-disable-next-line no-unused-vars
function onAddAltCityButtonClick() {
// As of v2.96 of WME, focusing and updating the alt address fields appears to be more difficult,
// perhaps due to shadow DOM containers. More research is needed. Until then, this feature is disabled.
// PLAYING AROUND WITH ATTEMPTS TO UPDATE THE ALT CITY INPUT.
// $('wz-button[class="add-alt-street-btn"]').click();
// $($('wz-text-input.alt-street-name')[0].shadowRoot).find('.wz-text-input-inner-container').click();
// $($('wz-text-input.alt-street-name')[0].shadowRoot).find('input').val('whatever').blur().change();
// PRE-WME v2.96 CODE. DELETE LATER.
// $('.full-address').click();
// $('.add-alt-street-btn').click();
// $('.alt-street-add').click(); // added by jm6087
// const streetName = $('form.address-form input.street-name').first().val();
// const $altStreetInput = $('div.add-alt-street-form input.alt-street-name').last(); // added by jm6087
// // const $altStreetInput = $('form.address-form div.add-alt-street-form input.alt-street-name').last(); // commented out by jm6087
// const $altCityInput = $('div.add-alt-street-form input.alt-city-name').last(); // added by jm6087
// // const $altCityInput = $('form.address-form div.add-alt-street-form input.alt-city-name').last(); // commented out by jm6087
// const $altEmptyCityCheckbox = $('form.address-form div.add-alt-street-form input.alt-address.empty-city').last();
// $altStreetInput.val(streetName).blur().change();
// if ($altEmptyCityCheckbox.is(':checked')) $altEmptyCityCheckbox.click();
// $altCityInput.val('').focus();
}
function onRoadTypeButtonClick(roadTypeAbbr) {
const segments = W.selectionManager.getSelectedFeatures();
const roadTypeVal = ROAD_TYPES[roadTypeAbbr].val;
let action;
if (segments.length > 1) {
action = new MultiAction();
action.setModel(W.model);
segments.forEach(segment => {
const subAction = new UpdateObject(segment.model, { roadType: roadTypeVal });
action.doSubAction(subAction);
});
action._description = I18n.t(
'save.changes_log.actions.UpdateObject.changed',
{
propertyName: I18n.t('objects.segment.fields.roadType'),
objectsString: I18n.t('objects.segment.multi', { count: segments.length }),
value: I18n.t('segment.road_types')[roadTypeVal]
}
);
} else {
action = new UpdateObject(segments[0].model, { roadType: roadTypeVal });
}
W.model.actionManager.add(action);
if (roadTypeAbbr === 'PLR' && isChecked('csClearNewPLRCheckBox') && typeof require !== 'undefined') {
setStreetAndCity(isChecked('csSetNewPLRCityCheckBox'));
} else if (roadTypeAbbr === 'PR' && isChecked('csClearNewPRCheckBox') && typeof require !== 'undefined') {
setStreetAndCity(isChecked('csSetNewPRCityCheckBox'));
} else if (roadTypeAbbr === 'RR' && isChecked('csClearNewRRCheckBox') && typeof require !== 'undefined') { // added by jm6087
setStreetAndCity(isChecked('csSetNewRRCityCheckbox')); // added by jm6087
} else if (roadTypeAbbr === 'PB' && isChecked('csClearNewPBCheckBox') && typeof require !== 'undefined') { // added by jm6087
setStreetAndCity(isChecked('csSetNewPBCityCheckBox')); // added by jm6087
}
}
function addRoadTypeButtons() {
const seg = W.selectionManager.getSelectedFeatures()[0].model;
if (seg.type !== 'segment') return;
const isPed = isPedestrianTypeSegment(seg);
const $dropDown = $(ROAD_TYPE_DROPDOWN_SELECTOR);
$('#csRoadTypeButtonsContainer').remove();
const $container = $('<div>', { id: 'csRoadTypeButtonsContainer', class: 'rth-btn-container' });
const $street = $('<div>', { id: 'csStreetButtonContainer', class: 'cs-rt-btn-container' });
const $highway = $('<div>', { id: 'csHighwayButtonContainer', class: 'cs-rt-btn-container' });
const $otherDrivable = $('<div>', { id: 'csOtherDrivableButtonContainer', class: 'cs-rt-btn-container' });
const $nonDrivable = $('<div>', { id: 'csNonDrivableButtonContainer', class: 'cs-rt-btn-container' });
const $pedestrian = $('<div>', { id: 'csPedestrianButtonContainer', class: 'cs-rt-btn-container' });
const divs = {
streets: $street,
highways: $highway,
otherDrivable: $otherDrivable,
nonDrivable: $nonDrivable,
pedestrian: $pedestrian
};
Object.keys(ROAD_TYPES).forEach(roadTypeKey => {
if (_settings.roadTypeButtons.indexOf(roadTypeKey) !== -1) {
const roadType = ROAD_TYPES[roadTypeKey];
const isDisabled = $dropDown[0].hasAttribute('disabled') && $dropDown[0].getAttribute('disabled') === 'true';
if (!isDisabled && ((roadType.category === 'pedestrian' && isPed) || (roadType.category !== 'pedestrian' && !isPed))) {
const $div = divs[roadType.category];
$div.append(
$('<div>', {
class: `btn btn-rth btn-rth-${roadTypeKey} btn-positive`,
title: I18n.t('segment.road_types')[roadType.val]
})
.text(_trans.roadTypeButtons[roadTypeKey].text)
.prop('checked', roadType.visible)
.data('key', roadTypeKey)
// TODO: change onRoadTypeButtonClick handler to work with element rather than data
.click(function rtbClick() { onRoadTypeButtonClick($(this).data('key')); })
);
}
}
});
if (isPed) {
$container.append($pedestrian);
} else {
$container.append($street).append($highway).append($otherDrivable).append($nonDrivable);
}
$dropDown.before($container);
}
function addRoutingTypeButtons() {
const $dropDown = $(ROUTING_TYPE_DROPDOWN_SELECTOR);
if ($dropDown.length > 0) {
const options = $dropDown.find('wz-option');
if (options.length === 3) {
const buttonInfos = [
{
text: '-1', value: options[0].value, toolTip: options[0].innerText, disabled: options[0].disabled
},
{
text: options[1].innerText, value: options[1].value, toolTip: '', disabled: options[1].disabled
},
{
text: '+1', value: options[2].value, toolTip: options[2].innerText, disabled: options[2].disabled
}
];
$('#csRoutingTypeContainer').remove();
// TODO css
const $form = $('<div>', { id: 'csRoutingTypeContainer', style: 'height:16px;padding-top:0px' });
for (let i = 0; i < buttonInfos.length; i++) {
const btnInfo = buttonInfos[i];
const $input = $('<input>', {
type: 'radio', name: 'routingRoadType', id: `routingRoadType${i}`, value: btnInfo.value
}).click(function onRouteTypeClick() {
$(ROUTING_TYPE_DROPDOWN_SELECTOR).val($(this).attr('value')).change();
});
if (btnInfo.disabled) $input.attr('disabled', 'true');
if (String(btnInfo.value) === String($dropDown.val())) $input.prop('checked', 'true');
$form.append(
// TODO css
$('<div>', {
class: 'controls-container',
style: 'float: left; margin-right: 10px;margin-left: 0px;padding-top: 0px;'
}).append(
$input,
$('<label>', {
// TODO css
for: `routingRoadType${i}`, style: 'padding-left: 20px;', title: btnInfo.toolTip
}).text(btnInfo.text)
)
);
}
$dropDown.before($form);
$dropDown.hide();
}
}
}
function isPLA(item) {
return (item.model.type === 'venue') && item.model.attributes.categories.indexOf('PARKING_LOT') > -1;
}
function addParkingSpacesButtons() {
const $dropDown = $(PARKING_SPACES_DROPDOWN_SELECTOR);
const selItems = W.selectionManager.getSelectedFeatures();
const item = selItems[0];
// If it's not a PLA, exit.
if (!isPLA(item)) return;
$('#csParkingSpacesContainer').remove();
const $div = $('<div>', { id: 'csParkingSpacesContainer' });
const dropdownDisabled = $dropDown.attr('disabled') === 'disabled';
const optionNodes = $(`${PARKING_SPACES_DROPDOWN_SELECTOR} option`);
for (let i = 0; i < optionNodes.length; i++) {
const $option = $(optionNodes[i]);
const text = $option.text();
const selected = $option.val() === $dropDown.val();
$div.append(
// TODO css
$('<div>', {
class: `btn waze-btn waze-btn-white${selected ? ' waze-btn-blue' : ''}${dropdownDisabled ? ' disabled' : ''}`,
style: 'margin-bottom: 5px; height: 22px; padding: 2px 8px 0px 8px; margin-right: 3px;'
})
.text(text)
.data('val', $option.val())
// eslint-disable-next-line func-names
.hover(() => { })
.click(function onParkingSpacesButtonClick() {
if (!dropdownDisabled) {
$(PARKING_SPACES_DROPDOWN_SELECTOR).val($(this).data('val')).change();
addParkingSpacesButtons();
}
})
);
}
$dropDown.before($div);
$dropDown.hide();
}
function addParkingCostButtons() {
const $dropDown = $(PARKING_COST_DROPDOWN_SELECTOR);
const selItems = W.selectionManager.getSelectedFeatures();
const item = selItems[0];
// If it's not a PLA, exit.
if (!isPLA(item)) return;
$('#csParkingCostContainer').remove();
const $div = $('<div>', { id: 'csParkingCostContainer' });
const dropdownDisabled = $dropDown.attr('disabled') === 'disabled';
const optionNodes = $(`${PARKING_COST_DROPDOWN_SELECTOR} option`);
for (let i = 0; i < optionNodes.length; i++) {
const $option = $(optionNodes[i]);
const text = $option.text();
const selected = $option.val() === $dropDown.val();
$div.append(
$('<div>', {
class: `btn waze-btn waze-btn-white${selected ? ' waze-btn-blue' : ''}${dropdownDisabled ? ' disabled' : ''}`,
// TODO css
style: 'margin-bottom: 5px; height: 22px; padding: 2px 8px 0px 8px; margin-right: 4px;'
})
.text(text !== '' ? text : '?')
.data('val', $option.val())
// eslint-disable-next-line func-names
.hover(() => { })
.click(function onParkingCostButtonClick() {
if (!dropdownDisabled) {
$(PARKING_COST_DROPDOWN_SELECTOR).val($(this).data('val')).change();
addParkingCostButtons();
}
})
);
}
$dropDown.before($div);
$dropDown.hide();
}
function addElevationButtons() {
const id = 'csElevationButtonsContainer';
if ($(`#${id}`).length === 0) {
const $dropDown = $(ELEVATION_DROPDOWN_SELECTOR);
if ($dropDown[0].getAttribute('disabled') === 'false') {
const baseClass = 'btn';
// TODO css
const style = 'height: 20px;padding-left: 8px;padding-right: 8px;margin-right: 4px;padding-top: 1px;';
// TODO css
const $div = $('<div>', { id, style: 'margin-bottom: 5px;' }).append(
$('<button>', { class: baseClass, style }).text('-').click(() => {
const level = parseInt($(ELEVATION_DROPDOWN_SELECTOR).val(), 10);
if (level > -8) { $(ELEVATION_DROPDOWN_SELECTOR).val(level - 1).change(); }
}),
$('<button>', { class: baseClass, style }).text(_trans.groundButtonText)
.click(() => {
const level = parseInt($(ELEVATION_DROPDOWN_SELECTOR).val(), 10);
if (level !== 0) { $(ELEVATION_DROPDOWN_SELECTOR).val(0).change(); }
}),
$('<button>', { class: baseClass, style }).text('+').click(() => {
const level = parseInt($(ELEVATION_DROPDOWN_SELECTOR).val(), 10);
if (level < 9) { $(ELEVATION_DROPDOWN_SELECTOR).val(level + 1).change(); }
})
);
// TODO css
$dropDown.css({ display: 'inline-block', width: '120px', marginRight: '10px' });
$dropDown.before($div);
$dropDown.detach();
$div.prepend($dropDown);
}
}
}
function addAddAltCityButton() {
// As of Beta WME 2.96, I can't figure out how to get the onAddAltCityButtonClick function to work properly so
// I'm removing this feature for now.
// const id = 'csAddAltCityButton';
// if (W.selectionManager.getSelectedFeatures()[0].model.type === 'segment' && $(`#${id}`).length === 0) {
// $('div.address-edit').prev('wz-label').append(
// $('<a>', {
// href: '#',
// // TODO css
// style: 'float: right;text-transform: none;'
// + 'font-family: "Helvetica Neue", Helvetica, "Open Sans", sans-serif;color: #26bae8;'
// + 'font-weight: normal;'
// }).text('Add Alt City').click(onAddAltCityButtonClick)
// );
// }
}
function addSwapPedestrianButton() {
const id = 'csSwapPedestrianContainer';
$(`#${id}`).remove();
const selectedFeatures = W.selectionManager.getSelectedFeatures();
if (selectedFeatures.length === 1 && selectedFeatures[0].model.type === 'segment') {
// TODO css
const $container = $('<div>', { id, style: 'white-space: nowrap;float: right;display: inline;' });
const $button = $('<div>', {
id: 'csBtnSwapPedestrianRoadType',
title: '',
// TODO css
style: 'display:inline-block;cursor:pointer;'
});
$button.append('<span class="fa fa-arrows-h" style="font-size:20px; color:#e84545;"></span>')
.attr({
title: 'Swap between driving-type and walking-type segments.\nWARNING!'
+ ' This will DELETE and recreate the segment. Nodes may need to be reconnected.'
});
$container.append($button);
const $label = $('wz-select[name="roadType"]').closest('form').find('wz-label').first();
// TODO css
$label.css({ display: 'inline' }).after($container);
$('#csBtnSwapPedestrianRoadType').click(() => {
if (_settings.warnOnPedestrianTypeSwap) {
_settings.warnOnPedestrianTypeSwap = false;
saveSettingsToStorage();
if (!confirm('This will DELETE the segment and recreate it. Any speed data will be lost,'
+ ' and nodes will need to be reconnected (if applicable).'
+ ' This message will only be displayed once. Continue?')) {
return;
}
}
// delete the selected segment
let segment = W.selectionManager.getSelectedFeatures()[0];
const oldGeom = segment.geometry.clone();
W.model.actionManager.add(new DelSeg(segment.model));
// create the replacement segment in the other segment type (pedestrian -> road & vice versa)
// Note: this doesn't work in a MultiAction for some reason.
const newRoadType = isPedestrianTypeSegment(segment.model) ? 1 : 5;
segment = new Segment({ geometry: oldGeom, roadType: newRoadType });
segment.state = OL.State.INSERT;
W.model.actionManager.add(new AddSeg(segment, {
createNodes: !0,
openAllTurns: W.prefs.get('enableTurnsByDefault'),
createTwoWay: W.prefs.get('twoWaySegmentsByDefault'),
snappedFeatures: [null, null]
}));
const newId = W.model.repos.segments.idGenerator.lastValue;
const newSegment = W.model.segments.getObjectById(newId);
W.selectionManager.setSelectedModels([newSegment]);
});
}
}
/* eslint-disable no-bitwise, no-mixed-operators */
function shadeColor2(color, percent) {
const f = parseInt(color.slice(1), 16);
const t = percent < 0 ? 0 : 255;
const p = percent < 0 ? percent * -1 : percent;
const R = f >> 16;
const G = f >> 8 & 0x00FF;
const B = f & 0x0000FF;
return `#${(0x1000000 + (Math.round((t - R) * p) + R) * 0x10000 + (Math.round((t - G) * p) + G)
* 0x100 + (Math.round((t - B) * p) + B)).toString(16).slice(1)}`;
}
/* eslint-enable no-bitwise, no-mixed-operators */
function buildRoadTypeButtonCss() {
const lines = [];
const useOldColors = _settings.useOldRoadColors;
Object.keys(ROAD_TYPES).forEach(roadTypeAbbr => {
const roadType = ROAD_TYPES[roadTypeAbbr];
const bgColor = useOldColors ? roadType.svColor : roadType.wmeColor;
let output = `.rth-btn-container .btn-rth-${roadTypeAbbr} {background-color:${
bgColor};box-shadow:0 2px ${shadeColor2(bgColor, -0.5)};border-color:${shadeColor2(bgColor, -0.15)};}`;
output += ` .rth-btn-container .btn-rth-${roadTypeAbbr}:hover {background-color:${
shadeColor2(bgColor, 0.2)}}`;
lines.push(output);
});
return lines.join(' ');
}
function injectCss() {
const css = [
// Road type button formatting
'.csRoadTypeButtonsCheckBoxContainer {margin-left:15px;}',
'.rth-btn-container {margin-bottom:5px;height:21px;}',
'.rth-btn-container .btn-rth {font-size:11px;line-height:20px;color:black;padding:0px 4px;height:20px;'
+ 'margin-right:2px;border-style:solid;border-width:1px;}',
buildRoadTypeButtonCss(),
'.btn.btn-rth:active {box-shadow:none;transform:translateY(2px)}',
'div .cs-rt-btn-container {float:left; margin: 0px 5px 5px 0px;}',
'#sidepanel-clicksaver .controls-container {padding:0px;}',
'#sidepanel-clicksaver .controls-container label {white-space: normal;}',
// Lock button formatting
'.btn-lh {cursor:pointer;padding:1px 6px;height:22px;border:solid 1px #c1c1c1;margin-right:3px;}',
'.btn.btn-lh.btn-lh-selected {background-color:#6999ae;color:white}',
'.btn.btn-lh.btn-lh-selected:hover {color:white}',
'.btn.btn-lh.disabled {color:#909090;background-color:#f7f7f7;}',
'.btn.btn-lh.btn-lh-selected.disabled {color:white;background-color:#6999ae;}',
'.cs-group-label {font-size: 11px; width: 100%; font-family: Poppins, sans-serif;'
+ ' text-transform: uppercase; font-weight: 700; color: #354148; margin-bottom: 6px;}'
].join(' ');
$(`<style type="text/css">${css}</style>`).appendTo('head');
}
function createSettingsCheckbox(id, settingName, labelText, titleText, divCss, labelCss, optionalAttributes) {
const $container = $('<div>', { class: 'controls-container' });
const $input = $('<input>', {
type: 'checkbox', class: 'csSettingsCheckBox', name: id, id, 'data-setting-name': settingName
}).appendTo($container);
const $label = $('<label>', { for: id }).text(labelText).appendTo($container);
// TODO css
if (divCss) $container.css(divCss);
// TODO css
if (labelCss) $label.css(labelCss);
if (titleText) $container.attr({ title: titleText });
if (optionalAttributes) $input.attr(optionalAttributes);
return $container;
}
function initUserPanel() {
const $roadTypesDiv = $('<div>', { class: 'csRoadTypeButtonsCheckBoxContainer' });
$roadTypesDiv.append(
createSettingsCheckbox('csUseOldRoadColorsCheckBox', 'useOldRoadColors', _trans.prefs.useOldRoadColors)
);
Object.keys(ROAD_TYPES).forEach(roadTypeAbbr => {
const roadType = ROAD_TYPES[roadTypeAbbr];
const id = `cs${roadTypeAbbr}CheckBox`;
const title = I18n.t('segment.road_types')[roadType.val];
$roadTypesDiv.append(
createSettingsCheckbox(id, 'roadType', title, null, null, null, {
'data-road-type': roadTypeAbbr
})
);
if (roadTypeAbbr === 'PLR' || roadTypeAbbr === 'PR' || roadTypeAbbr === 'RR' || roadTypeAbbr === 'PB') { // added RR & PB by jm6087
$roadTypesDiv.append(
// TODO css
createSettingsCheckbox(`csClearNew${roadTypeAbbr}CheckBox`, `setNew${roadTypeAbbr}StreetToNone`,
_trans.prefs.setStreetCityToNone, _trans.prefs.setStreetCityToNone_Title,
{ paddingLeft: '20px', marginRight: '4px' }, { fontStyle: 'italic' }),
createSettingsCheckbox(`csSetNew${roadTypeAbbr}CityCheckBox`, `setNew${roadTypeAbbr}City`,
_trans.prefs.setCityToConnectedSegCity, '',
{ paddingLeft: '30px', marginRight: '4px' }, { fontStyle: 'italic' })
);
}
});
const $tab = $('<li>', { title: argsObject.scriptName }).append(
$('<a>', { 'data-toggle': 'tab', href: '#sidepanel-clicksaver' }).append($('<span>').text('CS'))
);
const $panel = $('<div>', { class: 'tab-pane', id: 'sidepanel-clicksaver' }).append(
$('<div>', { class: 'side-panel-section>' }).append(
// TODO css
$('<div>', { style: 'margin-bottom:8px;' }).append(
$('<div>', { class: 'form-group' }).append(
$('<label>', { class: 'cs-group-label' }).text(_trans.prefs.dropdownHelperGroup),
$('<div>').append(
createSettingsCheckbox('csRoadTypeButtonsCheckBox', 'roadButtons',
_trans.prefs.roadTypeButtons)
).append($roadTypesDiv),
createSettingsCheckbox('csRoutingTypeCheckBox', 'routingTypeButtons',
_trans.prefs.routingTypeButtons),
createSettingsCheckbox('csElevationButtonsCheckBox', 'elevationButtons',
_trans.prefs.elevationButtons),
createSettingsCheckbox('csParkingCostButtonsCheckBox', 'parkingCostButtons',
_trans.prefs.parkingCostButtons),
createSettingsCheckbox('csParkingSpacesButtonsCheckBox', 'parkingSpacesButtons',
_trans.prefs.parkingSpacesButtons)
),
$('<label>', { class: 'cs-group-label' }).text('Time Savers'),
$('<div>', { style: 'margin-bottom:8px;' }).append(
// THIS IS CURRENTLY DISABLED
// createSettingsCheckbox('csAddAltCityButtonCheckBox', 'addAltCityButton',
// 'Show "Add alt city" button'),
isSwapPedestrianPermitted() ? createSettingsCheckbox('csAddSwapPedestrianButtonCheckBox',
'addSwapPedestrianButton', 'Show "Swap driving<->walking segment type" button') : ''
)
)
)
);
$panel.append(
// TODO css
$('<div>', { style: 'margin-top:20px;font-size:10px;color:#999999;' }).append(
$('<div>').text(`version ${argsObject.scriptVersion}${argsObject.scriptName.toLowerCase().indexOf('beta') > -1 ? ' beta' : ''}`),
$('<div>').append(
$('<a>', { href: argsObject.forumUrl, target: '__blank' }).text(_trans.prefs.discussionForumLinkText)
)
)
);
$('#user-tabs > .nav-tabs').append($tab);
$('#user-info > .flex-parent > .tab-content').append($panel);
// Add change events
$('#csRoadTypeButtonsCheckBox').change(function onRoadTypeButtonCheckChanged() {
if (this.checked) {
$('.csRoadTypeButtonsCheckBoxContainer').show();
} else {
$('.csRoadTypeButtonsCheckBoxContainer').hide();
}
saveSettingsToStorage();
});
$('.csSettingsCheckBox').change(function onSettingsCheckChanged() {
const { checked } = this;
const settingName = $(this).data('setting-name');
if (settingName === 'roadType') {
const roadType = $(this).data('road-type');
const array = _settings.roadTypeButtons;
const index = array.indexOf(roadType);
if (checked && index === -1) {
array.push(roadType);
} else if (!checked && index !== -1) {
array.splice(index, 1);
}
} else {
_settings[settingName] = checked;
}
saveSettingsToStorage();
});
}
function updateControls() {
if ($(ROAD_TYPE_DROPDOWN_SELECTOR).length > 0) {
if (isChecked('csRoadTypeButtonsCheckBox')) addRoadTypeButtons();
}
if ($(ROUTING_TYPE_DROPDOWN_SELECTOR && isChecked('csRoutingTypeCheckBox')).length > 0) {
addRoutingTypeButtons();
}
if ($(ELEVATION_DROPDOWN_SELECTOR).length > 0 && isChecked('csElevationButtonsCheckBox')) {
addElevationButtons();
}
if ($(PARKING_SPACES_DROPDOWN_SELECTOR).length > 0 && isChecked('csParkingSpacesButtonsCheckBox')) {
addParkingSpacesButtons(); // TODO - add option setting
}
if ($(PARKING_COST_DROPDOWN_SELECTOR).length > 0 && isChecked('csParkingCostButtonsCheckBox')) {
addParkingCostButtons(); // TODO - add option setting
}
}
function replaceWord(target, searchWord, replaceWithWord) {
return target.replace(new RegExp(`\\b${searchWord}\\b`, 'g'), replaceWithWord);
}
function titleCase(word) {
return word.charAt(0).toUpperCase() + word.substring(1).toLowerCase();
}
function mcCase(word) {
return word.charAt(0).toUpperCase() + word.charAt(1).toLowerCase()
+ word.charAt(2).toUpperCase() + word.substring(3).toLowerCase();
}
function upperCase(word) {
return word.toUpperCase();
}
function processSubstring(target, substringRegex, processFunction) {
const substrings = target.match(substringRegex);
if (substrings) {
for (let idx = 0; idx < substrings.length; idx++) {
const substring = substrings[idx];
const newSubstring = processFunction(substring);
target = replaceWord(target, substring, newSubstring);
}
}
return target;
}
function onPaste(e) {
const targetNode = e.target;
if (targetNode.name === 'streetName' || targetNode.className.indexOf('street-name') > -1) {
// Get the text that's being pasted.
let pastedText = e.clipboardData.getData('text/plain');
// If pasting text in ALL CAPS...
if (/^[^a-z]*$/.test(pastedText)) {
[
// Title case all words first.
[/\b[a-zA-Z]+(?:'S)?\b/g, titleCase],
// Then process special cases.
[/\bMC\w+\b/ig, mcCase], // e.g. McCaulley
[/\b(?:I|US|SH|SR|CH|CR|CS|PR|PS)\s*-?\s*\d+\w*\b/ig, upperCase], // e.g. US-25, US25
/* eslint-disable-next-line max-len */
[/\b(?:AL|AK|AS|AZ|AR|CA|CO|CT|DE|DC|FM|FL|GA|GU|HI|ID|IL|IN|IA|KS|KY|LA|ME|MH|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|MP|OH|OK|OR|PW|PA|PR|RI|SC|SD|TN|TX|UT|VT|VI|VA|WA|WV|WI|WY)\s*-?\s*\d+\w*\b/ig, upperCase], // e.g. WV-52
[/\b(?:NE|NW|SE|SW)\b/ig, upperCase]
].forEach(item => {
pastedText = processSubstring(pastedText, item[0], item[1]);
});
// Insert new text in the focused node.
document.execCommand('insertText', false, pastedText);
// Prevent the default paste behavior.
e.preventDefault();
return false;
}
}
return true;
}
function getTranslationObject() {
if (argsObject.useDefaultTranslation) {
return DEFAULT_TRANSLATION;
}
let locale = I18n.currentLocale().toLowerCase();
if (!argsObject.translations.hasOwnProperty(locale)) {
locale = 'en-us';
}
return argsObject.translations[locale];
}
function errorHandler(callback) {
try {
callback();
} catch (ex) {
console.error(`${argsObject.scriptName}:`, ex);
}
}
function init() {
UpdateObject = require('Waze/Action/UpdateObject');
UpdateFeatureAddress = require('Waze/Action/UpdateFeatureAddress');
MultiAction = require('Waze/Action/MultiAction');
AddSeg = require('Waze/Action/AddSegment');
Segment = require('Waze/Feature/Vector/Segment');
DelSeg = require('Waze/Action/DeleteSegment');
_trans = getTranslationObject();
Object.keys(ROAD_TYPES).forEach(rtName => {
ROAD_TYPES[rtName].text = _trans.roadTypeButtons[rtName].text;
});
Object.keys(DIRECTIONS).forEach(d => {
DIRECTIONS[d].text = _trans.directionButtons[d].text;
DIRECTIONS[d].title = _trans.directionButtons[d].title;
});
document.addEventListener('paste', onPaste);
// check for changes in the edit-panel
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
for (let i = 0; i < mutation.addedNodes.length; i++) {
const addedNode = mutation.addedNodes[i];
if (addedNode.nodeType === Node.ELEMENT_NODE) {
if (addedNode.querySelector(ROAD_TYPE_DROPDOWN_SELECTOR)) {
if (isChecked('csRoadTypeButtonsCheckBox')) addRoadTypeButtons();
if (isSwapPedestrianPermitted() && isChecked('csAddSwapPedestrianButtonCheckBox')) {
addSwapPedestrianButton();
}
}
if (addedNode.querySelector(ROUTING_TYPE_DROPDOWN_SELECTOR) && isChecked('csRoutingTypeCheckBox')) {
addRoutingTypeButtons();
}
if (addedNode.querySelector(ELEVATION_DROPDOWN_SELECTOR) && isChecked('csElevationButtonsCheckBox')) {
addElevationButtons();
}
if (addedNode.querySelector(PARKING_SPACES_DROPDOWN_SELECTOR) && isChecked('csParkingSpacesButtonsCheckBox')) {
addParkingSpacesButtons(); // TODO - add option setting
}
if (addedNode.querySelector(PARKING_COST_DROPDOWN_SELECTOR)
&& isChecked('csParkingCostButtonsCheckBox')) {
addParkingCostButtons(); // TODO - add option setting
}
if ($(addedNode).find('div.address-edit').prev('wz-label').length && isChecked('csAddAltCityButtonCheckBox')) {
addAddAltCityButton();
}
}
}
});
});
observer.observe(document.getElementById('edit-panel'), { childList: true, subtree: true });
initUserPanel();
loadSettingsFromStorage();
injectCss();
W.prefs.on('change:isImperial', () => errorHandler(() => { initUserPanel(); loadSettingsFromStorage(); }));
updateControls(); // In case of PL w/ segments selected.
W.selectionManager.events.register('selectionchanged', null, () => errorHandler(updateControls));
logDebug('Initialized');
}
//---------------------------------------------------------------------------------------------
// The following code (Quick Alt Delete) was originally written by jangliss.
// Huge thanks to him for the help!
//---------------------------------------------------------------------------------------------
function updateAltStreetCtrls() {
if (W.selectionManager.getSelectedFeatures().length > 0) {
const selItems = W.selectionManager.getSelectedFeatures();
if (selItems.length > 0 && selItems[0].model.type === 'segment') {
const $idElements = $('.add-alt-street-form .alt-street');
const $liElements = $('li.alt-street');
for (let i = 0; i < $idElements.length; i++) {
const $idElem = $idElements.eq(i);
const $liElem = $liElements.eq(i);
if ($liElem.find('i').length === 0) { // prevent duplicate entries
$liElem.append(
$('<i>', { class: 'fa fa-times-circle' })
// TODO css
.css({ cursor: 'pointer' })
.data('id', $idElem.data('id'))
// eslint-disable-next-line no-use-before-define
.click(wmeAltStreetRemove)
);
}
}
}
}
}
function wmeAltStreetRemove(elemClicked) {
const altID = parseInt($(elemClicked.currentTarget).data('id'), 10);
const selectedObjs = W.selectionManager.getSelectedFeatures();
selectedObjs.forEach(element => {
if (element.model.type === 'segment') {
const segment = element.model;
if (segment.attributes.streetIDs.indexOf(altID) !== -1) {
const newStreets = [];
segment.attributes.streetIDs.forEach(sID => {
if (altID !== sID) newStreets.push(sID);
});
const sUpdate = new UpdateObject(segment, { streetIDs: newStreets });
W.model.actionManager.add(sUpdate);
updateAltStreetCtrls();
}
}
});
}
function initWmeQuickAltDel() {
W.selectionManager.events.register('selectionchanged', null, () => errorHandler(updateAltStreetCtrls));
W.model.actionManager.events.register('afterundoaction', null, () => errorHandler(updateAltStreetCtrls));
W.model.actionManager.events.register('hasActions', null, () => errorHandler(() => setTimeout(updateAltStreetCtrls, 250)));
W.model.actionManager.events.register('noActions', null, () => errorHandler(() => setTimeout(updateAltStreetCtrls, 250)));
W.model.actionManager.events.register('afteraction', null, () => errorHandler(updateAltStreetCtrls));
const observer = new MutationObserver((mutations => {
mutations.forEach(mutation => {
if ($(mutation.target).hasClass('preview')) updateAltStreetCtrls();
});
}));
observer.observe(document.getElementById('edit-panel'), { childList: true, subtree: true });
}
function bootstrap() {
if (typeof require !== 'undefined' && W && W.loginManager && W.loginManager.events.register && W.map && W.loginManager.user) {
logDebug('Initializing...');
init();
initWmeQuickAltDel();
} else {
logDebug('Bootstrap failed. Trying again...');
setTimeout(bootstrap, 250);
}
}
// Not sure if the document.ready is necessary but I'm leaving it because of some random errors
// that people were having with "require is not defined". I tried several things to fix it and
// I'm leaving those things, though all may not be needed.
$(document).ready(() => {
logDebug('Bootstrap...');
bootstrap();
});
} // END Main function (code to be injected)
function injectMain(argsObject) {
if (typeof require !== 'undefined' && typeof $ !== 'undefined') {
const scriptElem = document.createElement('script');
scriptElem.textContent = `(function(){${main.toString()}\n main(${JSON.stringify(argsObject).replace('\'', '\\\'')})})();`;
scriptElem.setAttribute('type', 'application/javascript');
document.body.appendChild(scriptElem);
} else {
setTimeout(() => injectMain(argsObject), 250);
}
}
function setValue(object, path, value) {
const pathParts = path.split('.');
for (let i = 0; i < pathParts.length - 1; i++) {
const pathPart = pathParts[i];
if (pathPart in object) {
object = object[pathPart];
} else {
object[pathPart] = {};
object = object[pathPart];
}
}
object[pathParts[pathParts.length - 1]] = value;
}
function convertTranslationsArrayToObject(arrayIn) {
const translations = {};
let iRow;
let iCol;
const languages = arrayIn[0].map(lang => lang.toLowerCase());
for (iCol = 1; iCol < languages.length; iCol++) {
translations[languages[iCol]] = {};
}
for (iRow = 1; iRow < arrayIn.length; iRow++) {
const row = arrayIn[iRow];
const propertyPath = row[0];
for (iCol = 1; iCol < row.length; iCol++) {
setValue(translations[languages[iCol]], propertyPath, row[iCol]);
}
}
return translations;
}
function loadTranslations() {
if (typeof $ === 'undefined') {
setTimeout(loadTranslations, 250);
console.debug('ClickSaver:', 'jQuery not ready. Retry loading translations...');
} else {
// This call retrieves the data from the translations spreadsheet and then injects
// the main code into the page. If the spreadsheet call fails, the default English
// translation is used.
const args = {
scriptName: SCRIPT_NAME,
scriptVersion: SCRIPT_VERSION,
forumUrl: FORUM_URL
};
$.getJSON(`${TRANSLATIONS_URL}?${DEC(API_KEY)}`).then(res => {
args.translations = convertTranslationsArrayToObject(res.values);
console.debug('ClickSaver:', 'Translations loaded.');
}).fail(() => {
console.error('ClickSaver: Error loading translations spreadsheet. Using default translation (English).');
args.useDefaultTranslation = true;
}).always(() => {
// Leave this document.ready function. Some people randomly get a "require is not defined" error unless the injectMain function
// is called late enough. Even with a "typeof require !== 'undefined'" check.
$(document).ready(() => {
injectMain(args);
});
});
}
}
// This function requires WazeWrap so it must be called outside of the injected code, as
// WazeWrap is not guaranteed to be available in the page's scope.
function addToggleDrawNewRoadsAsTwoWayShortcut() {
new WazeWrap.Interface.Shortcut('ToggleTwoWayNewSeg', 'Toggle new segment two-way drawing',
'editing', 'editToggleNewSegTwoWayDrawing', EXTERNAL_SETTINGS.toggleTwoWaySegDrawingShortcut,
() => { $('wz-checkbox[name="twoWaySegmentsByDefault"]').click(); }, null).add();
}
function sandboxLoadSettings() {
const loadedSettings = JSON.parse(localStorage.getItem(EXTERNAL_SETTINGS_NAME)) || {};
EXTERNAL_SETTINGS.toggleTwoWaySegDrawingShortcut = loadedSettings.toggleTwoWaySegDrawingShortcut || '';
addToggleDrawNewRoadsAsTwoWayShortcut();
$(window).on('beforeunload', () => sandboxSaveSettings());
}
function sandboxSaveSettings() {
let keys = '';
const { shortcut } = W.accelerators.Actions.ToggleTwoWayNewSeg;
if (shortcut) {
if (shortcut.altKey) keys += 'A';
if (shortcut.shiftKey) keys += 'S';
if (shortcut.ctrlKey) keys += 'C';
if (keys.length) keys += '+';
if (shortcut.keyCode) keys += shortcut.keyCode;
}
EXTERNAL_SETTINGS.toggleTwoWaySegDrawingShortcut = keys;
localStorage.setItem(EXTERNAL_SETTINGS_NAME, JSON.stringify(EXTERNAL_SETTINGS));
}
function sandboxBootstrap() {
if (WazeWrap && WazeWrap.Ready) {
WazeWrap.Interface.ShowScriptUpdate(SCRIPT_NAME, SCRIPT_VERSION, UPDATE_MESSAGE, FORUM_URL);
sandboxLoadSettings();
} else {
setTimeout(sandboxBootstrap, 250);
}
}
// Go ahead and start loading translations, and inject the main code into the page.
loadTranslations();
// Start the "sandboxed" code.
sandboxBootstrap();