您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a clickable '+' icon to top left of each workout step that creates a copy of that step.
// ==UserScript== // @name Garmin Connect Workout - Copy Workout Step // @description Adds a clickable '+' icon to top left of each workout step that creates a copy of that step. // @author Do Om // @namespace https://connect.garmin.com // @include https://connect.garmin.com/modern/workout/* // @include https://connect.garmin.com/modern/workout/edit/* // @version 1 // @grant none // ==/UserScript== var style = document.createElement('style') style.innerHTML = ` #cloneBtn { float: left; position: relative; margin-left: 6px; margin-top: 1px%; cursor: pointer; }`; document.head.appendChild(style) idList = [] cloned = false workoutType = null workoutSelect = { "workout-step-warm": 0, "workout-step-run": 1, "workout-step-recover": 2, "workout-step-rest": 3, "workout-step-cool": 4, "workout-step-other": 5 } durationSelect = { 1:"step-duration-time", 2:"step-duration-distance", 3:"step-duration-lap-button", 4:"step-duration-cal", 5:"step-duration-hr" } durationIdx = null durationTime = null durationUnit = null //#duration-distance durationValue = null durationOption = null targetExists = false stepTargetValue = null stepTargetIdx = null heartRateIdx = null targetValue = null targetFrom = null targetTo = null function createCloneBtn() { var div = document.createElement('span') div.setAttribute('id', 'cloneBtn') div.setAttribute('class', 'has-tooltip') //div.classList.add('pull-left') let clone = document.createElement('a') clone.classList.add('cloneBtn') clone.innerHTML = '+' div.append(clone) clone.onclick = function() { cloned = true let currStep = this.parentNode.parentNode workoutType = currStep.classList[1] //TODO: change these to value instead of index let duration = currStep.querySelector('.select-step-duration') durationIdx = duration.selectedIndex let durationDiv = currStep.querySelector('.step-duration') switch(duration.value) { case 'time': durationValue = durationDiv.querySelector('input[name="duration-time"]').value durationOption = null break case 'distance': durationValue = durationDiv.querySelector('input[name="duration-distance"]').value durationOption = durationDiv.querySelector('select[name="distanceUnit"]').value break case 'lap.button': durationValue = null durationOption = null break case 'calories': durationValue = durationDiv.querySelector('input[name="duration-calories"]').value durationOption = null break case 'heart.rate.zone': durationOption = durationDiv.querySelector('select[name="heart-rate-select"]').value durationValue = durationDiv.querySelector('input[name="duration-heart-rate-custom"]').value break default: durationValue = null durationOption = null break } var stepTargetSelect = currStep.querySelector('.select-step-target') stepTargetValue = stepTargetSelect.value stepTargetIdx = stepTargetSelect.selectedIndex var stepTargetDiv = currStep.querySelector('.step-target') addMore = currStep.querySelector('#add-step-target') if(addMore.classList.contains('hide') && stepTargetSelect.value != 'no.target') { targetValue = stepTargetSelect.value switch(targetValue) { case 'pace.zone': targetExists = true //console.log('pace.zone') targetFrom = stepTargetDiv.querySelector('input[name="target-pace-from"]').value targetTo = stepTargetDiv.querySelector('input[name="target-pace-to"]').value break case 'speed.zone': targetExists = true targetFrom = stepTargetDiv.querySelector('input[name="target-speed-from"]').value targetTo = stepTargetDiv.querySelector('input[name="target-speed-to"]').value break case 'cadence': targetExists = true targetFrom = stepTargetDiv.querySelector('input[name="target-cadence-from"]').value targetTo = stepTargetDiv.querySelector('input[name="target-cadence-to"]').value break case 'heart.rate.zone': targetExists = true heartRateIdx = stepTargetSelect.selectedIndex if(heartRateIdx == 9) { targetFrom = stepTargetDiv.querySelector('input[name="target-heart-rate-custom-from"]').value targetTo = stepTargetDiv.querySelector('input[name="target-heart-rate-custom-to"]').value } break default: targetExists = false break } } else { //console.log('nottarget') } var newStepBtn = document.querySelector('#new-step') newStepBtn.click() } return div } var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if(mutation.target.classList.contains('workout-edit')) { //if (!mutation.addedNodes) return var steps = mutation.target.querySelectorAll('.workout-step') for (var i = 0; i < steps.length; i++) { var node = steps[i] if(node.querySelector('#cloneBtn') == null) { node.prepend(createCloneBtn()) } } } for (var i = 0; i < mutation.addedNodes.length; i++) { var node = mutation.addedNodes[i] if(node.classList.contains('workout-step') && cloned == true) { let workoutIdx = workoutSelect[workoutType] let stepType = node.querySelector('.select-step-type') stepType.selectedIndex = workoutIdx stepType.dispatchEvent(new Event('change', { bubbles: true })) let stepDefine = node.querySelector('.step-define') let duration = node.querySelector('.select-step-duration') duration.selectedIndex = durationIdx duration.dispatchEvent(new Event('change', { bubbles: true })) let durationDiv = node.querySelector('.step-duration') switch(duration.value) { case 'time': { let input = durationDiv.querySelector('input[name="duration-time"]') input.value = durationValue input.dispatchEvent(new Event('change', { bubbles: true })) } break case 'distance': { let input = durationDiv.querySelector('input[name="duration-distance"]') input.value = durationValue input.dispatchEvent(new Event('change', { bubbles: true })) let option = durationDiv.querySelector('select[name="distanceUnit"]') option.value = durationOption option.dispatchEvent(new Event('change', { bubbles: true })) } break case 'lap.button': break case 'calories': { let input = durationDiv.querySelector('input[name="duration-calories"]') input.value = durationValue input.dispatchEvent(new Event('change', { bubbles: true })) } break case 'heart.rate.zone': { let option = durationDiv.querySelector('select[name="heart-rate-select"]') option.value = durationOption option.dispatchEvent(new Event('change', { bubbles: true })) let input = durationDiv.querySelector('input[name="duration-heart-rate-custom"]') input.value = durationValue input.dispatchEvent(new Event('change', { bubbles: true })) } break default: break } var stepTargetSelect = node.querySelector('.select-step-target') var stepTargetDiv = node.querySelector('.step-target') if(targetExists == true) { let addMore = node.querySelector('#add-step-target') addMore.click() } stepTargetSelect.selectedIndex = stepTargetIdx stepTargetSelect.dispatchEvent(new Event('change', { bubbles: true })); stepTargetDiv.dispatchEvent(new Event('change', { bubbles: true })); let targetValue = stepTargetSelect.value switch(targetValue) { case 'pace.zone': { let inputFrom = stepTargetDiv.querySelector('input[name="target-pace-from"]') inputFrom.value = targetFrom inputFrom.dispatchEvent(new Event('change', { bubbles: true })) let inputTo = stepTargetDiv.querySelector('input[name="target-pace-to"]') inputTo.value = targetTo inputTo.dispatchEvent(new Event('change', { bubbles: true })) } break case 'speed.zone': { let inputFrom = stepTargetDiv.querySelector('input[name="target-speed-from"]') inputFrom.value = targetFrom inputFrom.dispatchEvent(new Event('change', { bubbles: true })) let inputTo = stepTargetDiv.querySelector('input[name="target-speed-to"]') inputTo.value = targetTo inputTo.dispatchEvent(new Event('change', { bubbles: true })) } break case 'cadence': { let inputFrom = stepTargetDiv.querySelector('input[name="target-cadence-from"]') inputFrom.value = targetFrom inputFrom.dispatchEvent(new Event('change', { bubbles: true })) let inputTo = stepTargetDiv.querySelector('input[name="target-cadence-to"]') inputTo.value = targetTo inputTo.dispatchEvent(new Event('change', { bubbles: true })) } break case 'heart.rate.zone': { if(heartRateIdx == 9) { let inputFrom = stepTargetDiv.querySelector('input[name="target-heart-rate-custom-from"]') inputFrom.value = targetFrom inputFrom.dispatchEvent(new Event('change', { bubbles: true })) let inputTo = stepTargetDiv.querySelector('input[name="target-heart-rate-custom-to"]') inputTo.value = targetTo inputTo.dispatchEvent(new Event('change', { bubbles: true })) } } break default: break } // Add clone button let div = createCloneBtn() node.prepend(div) } targetExists = false cloned = false } }) }) observer.observe(document.body, { childList: true , subtree: true , attributes: false , characterData: false }) //observer.disconnect()