try it
Fra
// ==UserScript==
// @name 阿里3D项目-标注工具
// @namespace http://tampermonkey.net/
// @version 2024.2.4.1 Beta
// @description try it
// @author You
// @match https://ads.aligenie.com/labeltools?type=BATCHWORK&*
// @icon https://www.google.com/s2/favicons?sz=64&domain=aligenie.com
// @grant none
// ==/UserScript==
(async function() {
'use strict';
/**==================================
快捷键:
【Q】 ==> 俯视角左调
【E】 ==> 俯视角右调
【鼠标中键】 ==> 进入/退出三视图编辑模式
【空格】 ==> 帧数前切(选中个体时,控制在关联帧范围;三视图总览表→批量前切)
【`】 ==> 帧数后切(选中个体时,控制在关联帧范围;三视图总览表→批量后切)
【Ctrl + Y】 ==> 开启/关闭自动删除
自动生效:
- 个体属性面板自动保存
==================================**/
function awaitLoad() {
return new Promise((res, rej) => {
let observer = new MutationObserver((mrs) => {
// console.log('===')
// console.log(mrs)
mrs.some((mr) => {
[...mr.addedNodes].some((an) => {
let findEl = document.querySelector('.index_container__v2bdb')
if(an?.nodeName === 'DIV' && findEl) {
// console.log(an)
observer.disconnect();
res(true)
}
})
})
});
observer.observe(document, { childList: true, subtree: true})
})
}
if(await awaitLoad()) showMessage('工具已启用')
let saveRecord = []
let isDel = false
new MutationObserver((mrs) => {
if(mrs[0].addedNodes[0]?.nodeName !== '#text') console.log(mrs)
mrs.some((mr) => {
[...mr.addedNodes].some((an) => {
//an.nodeName !== '#text' && console.log(an)
if(an?.className?.includes('index_QuestionTabs__kk6nt')) {
an.style.border = '2px solid red'
new MutationObserver((mrs) => {
mrs.some((mr) => {
if(mr.type === 'attributes' && mr.target.className.includes('ant-radio-wrapper ant-radio-wrapper-checked') && mr.oldValue === 'ant-radio-wrapper') {
console.log('new select')
document.querySelector('.index_question__4Tm9w').querySelector('.ant-btn').click()
let time = Date.now()
// console.log('gen'+ time)
saveRecord.push([time, 0])
}
})
}).observe(an, { childList: true, subtree: true, attributes: true, attributeOldValue: true});
return true;
}
if(an?.className?.includes('ant-message-notice') && an.innerText === '只允许切换锚点类型') an.remove()
if(isDel && an?.className?.includes('ant-modal-root') && an.querySelector('.ant-modal-body').innerText === '当前组内共有1个对象,删除后不可恢复,请谨慎操作!') an.querySelector('.ant-btn.ant-btn-primary.ant-btn-sm').click()
if(isDel && an?.className?.includes('ant-table-row ant-table-row-level-0 common_rowBgColor__CUySE')) {
let tdIdx;
[...document.querySelector('.ant-table-thead').children[0].children].find((item, idx) => {
if(item.innerText == '对象数') tdIdx = idx
});
let delRow = [...document.querySelectorAll('.ant-table-row')][0];
console.log('del '+delRow.dataset.rowKey);
console.log([...delRow.querySelectorAll('span')].at(-1));
[...delRow.querySelectorAll('span')].at(-1).click()
return true
}
});
})
}).observe(document, { childList: true, subtree: true, attributes: true, attributeOldValue: true})
// let attrMap = {
// '物体形态': 'body_shape',
// '形变体': 'soft',
// '刚体': 'rigid',
// '标注类别': 'type',
// '小汽车': 'Car',
// '货车/卡车': 'Truck',
// '公交车': 'Bus',
// '施工车': 'Construction',
// '三轮车': 'Tricycle',
// '骑二轮车的人': 'Cyclist',
// '行人': 'Pedestrian',
// '其他车辆': 'Other',
// '残影属性': 'blur',
// '分离': 'blur',
// '粘连': 'sticking',
// '无残影': 'no',
// ignore: 'ignore',
// '鬼影': 'multi-reflection',
// }
let attrMap = {
soft:'形变体',
rigid: '刚体',
}
const originalXHR = window.XMLHttpRequest;
window.XMLHttpRequest = function () {
var xhr = new originalXHR();
xhr.addEventListener('readystatechange', function () {
if (xhr.responseURL === 'https://ads.aligenie.com/api/item/ItemServiceI/linkageSaveItems') {
let saveRecordItem = null
let findRes = saveRecord.some((item) => {
if(item[1] == 0) {
saveRecordItem = item
return true
}
})
if(!findRes) return
// if(xhr.readyState === 2) {
// console.log('xhr', xhr);
// }
if(saveRecordItem && xhr.readyState === 4 && xhr.status == 200) {
let {retMsg, success, } = JSON.parse(xhr.responseText)
if(retMsg === 'success' && success === true) {
let res = JSON.parse(xhr.responseText)
let labels =JSON.parse(res.retValue.itemVO).items[0].labels
// console.log('itemOV',JSON.parse(res.retValue.itemVO).items)
// console.log('labels',labels)
showMessage(`修改完成(物体形态-${attrMap[labels.body_shape]})`, {type: 'success'})
saveRecordItem[1] = 1
saveRecord.some((item, idx) => {
if(item[0] === saveRecordItem[0]) {
saveRecord.splice(idx, 1)
console.log('删除'+ saveRecordItem[0])
}
})
console.log('请求', saveRecord)
}
} else if(xhr.readyState === 4) {
console.log('请求响应失败')
showMessage('保存失败', {type: 'error'})
saveRecordItem[1] = 2
}
// console.log('xhr', xhr);
// console.log('res', JSON.parse(xhr.responseText));
}
});
return xhr;
};
window.addEventListener('keydown', (e) => {
//console.log(e.keyCode, e)
if(e.keyCode == 89 && e.ctrlKey) {
isDel = !isDel
showMessage(`${isDel ? '开启' : '关闭'}:列表自动删除`)
}
if(e.keyCode == 81) {
document.body.dispatchEvent(new KeyboardEvent("keydown", {
key: "r",
keyCode: 82,
code: "KeyR",
bubbles: true, // 使事件冒泡
cancelable: true // 使事件可以被取消
}));
}
if(e.keyCode == 69) {
document.body.dispatchEvent(new KeyboardEvent("keydown", {
key: "t",
keyCode: 84,
code: "KeyT",
bubbles: true, // 使事件冒泡
cancelable: true // 使事件可以被取消
}));
}
if(e.keyCode == 32 || e.keyCode == 192) {
if(document.querySelector('.index_view__MhyQ9')) {
let keydownMap = {
32: {
key: "ArrowRight",
keyCode: 39,
code: "ArrowRight",
bubbles: true,
cancelable: true,
ctrlKey: true,
shiftKey: true,
},
192: {
key: "ArrowLeft",
keyCode: 37,
code: "ArrowLeft",
bubbles: true,
cancelable: true,
ctrlKey: true,
shiftKey: true,
}
}
document.body.dispatchEvent(new KeyboardEvent("keydown", keydownMap[e.keyCode]));
} else if(document.querySelector('.index_container__v2bdb').children.length || document.querySelector('.index_small-container__aVxtr')){
turn(e.keyCode == 32 ? 'right' : 'left')
function turn(direction) {
let visiblePages = [...document.querySelector('.index_trackItem__6QT87').querySelectorAll('.index_track-rect__AvEjU')];
if(document.querySelector('.index_absolute-center__GLVXE')) { //判断是否超出分页区
let curPageDom = document.querySelector('.index_absolute-center__GLVXE').parentElement
let curPage = document.querySelector('.index_absolute-center__GLVXE').innerText -0
visiblePages.some((curPage, idx) => {
if(curPage === curPageDom) {
let pages = direction === 'right' ? visiblePages.slice(idx+1) : visiblePages.slice(0, idx).reverse()
if(!pages.length) { //处理首页和末页
let {width, left} = document.querySelector('.index_scroll-line__GHJXu').children[0].style //progress bar
if((direction === 'right' && parseInt(/(.*)%/.exec(width)[1]) + parseInt(/(.*)%/.exec(left)[1]) !== 100) || (direction === 'left' && left !== '0%')) { //判断是否需要更新预览区
updatePages(direction)
turn(direction)
return true
}
}
pages.some((nextPage, nextIdx) => {
if(nextPage.className.includes('index_relation__pabdZ') || nextPage.className.includes('index_key-relation__qe-c4')) {
nextPage.click()
return true
}
if(nextIdx == pages.length-1) {
let {width, left} = document.querySelector('.index_scroll-line__GHJXu').children[0].style //progress bar
if(parseInt(/(.*)%/.exec(width)[1]) + parseInt(/(.*)%/.exec(left)[1]) !== 100) { //判断是否需要更新预览区
updatePages(direction)
turn(direction)
return true
}
}
})
return true
}
let {width, left} = document.querySelector('.index_scroll-line__GHJXu').children[0].style //progress bar
if(direction === 'right' && curPage === curPageDom && idx == visiblePages.length-1) { //当前页位于分页区末页
if(parseInt(/(.*)%/.exec(width)[1]) + parseInt(/(.*)%/.exec(left)[1]) == 100) return true
updatePages(direction)
turn(direction)
return true
} else if(direction === 'left' && curPage === curPageDom && idx == 0) { //当前页位于首页
if(left == '0%') return true //判断是否需要更新分页区
updatePages(direction)
turn(direction)
return true
}
})
} else {
let pages = direction === 'right' ? visiblePages : visiblePages.reverse()
console.log('pages', pages)
pages.some((curPage, idx) => {
if(curPage.className.includes('index_relation__pabdZ') || curPage.className.includes('index_key-relation__qe-c4')) {
console.log(curPage)
curPage.click()
return true
}
let {width, left} = document.querySelector('.index_scroll-line__GHJXu').children[0].style //progress bar
if(direction === 'right' && idx == pages.length-1 && (parseInt(/(.*)%/.exec(width)[1]) + parseInt(/(.*)%/.exec(left)[1]) !== 100)) {
updatePages(direction)
turn(direction)
return true
} else if(direction === 'left' && idx == pages.length-1 && left !== '0%') { //遍历至首页 且 需要更新分页区
updatePages(direction)
turn(direction)
return true
}
})
}
function updatePages(direction) {
let viewTurnBtn = document.querySelector('.index_scroll-bar__V5IcI').querySelectorAll('.anticon')[direction === 'left' ? 0 : 1]
viewTurnBtn.click()
}
}
} else if(!document.querySelector('.index_container__v2bdb').children.length && !document.querySelector('.index_small-container__aVxtr')){
let keydownMap = {
32: {
key: "ArrowRight",
keyCode: 39,
code: "ArrowRight",
bubbles: true,
cancelable: true,
ctrlKey: true,
},
192: {
key: "ArrowLeft",
keyCode: 37,
code: "ArrowLeft",
bubbles: true,
cancelable: true,
ctrlKey: true,
}
}
document.body.dispatchEvent(new KeyboardEvent("keydown", keydownMap[e.keyCode]));
}
}
});
window.addEventListener('mousedown', (e) => {
if(e.button == 1) {
document.body.dispatchEvent(new KeyboardEvent("keydown", {
key: "e",
keyCode: 69, // 对应'E'键的keyCode
code: "KeyE",
ctrlKey: true,
shiftKey: true,
bubbles: true, // 使事件冒泡
cancelable: true // 使事件可以被取消
}))
}
})
/**
* @description 展示消息框
* @param {string} message 展示内容
* @param {object} [config] 配置对象
* @param {string} [config.type='default'] 内容类型(可选值:'default'、'success'、'warning'、'error')
* @param {number} [showTime=3000] 展示时间
* @param {string} [direction='top]' 展示的位置(可选值:'top'、'top left'、'left'、'top right'、'right'、'bottom'、'bottom left'、'bottom right')
* @return {void}
*/
function showMessage(message, config) { //type = 'default', showTime = 3000, direction
let MessageWrap = document.createElement('div')
MessageWrap.className = 'messageWrap'
setElStyle(MessageWrap, {
position: 'absolute',
zIndex: '9999'
})
let MessageBox = document.createElement('div')
MessageBox.innerText = message
let closeBtn = document.createElement('div')
closeBtn.textContent = '×'
closeBtn.addEventListener('click', MessageBox.remove.bind(MessageBox)) //关闭消息提示
setElStyle(MessageBox, {
position: 'relative',
minWidth: '200px',
marginTop: '5px',
padding: '6px 50px',
lineHeight: '25px',
backgroundColor: 'pink',
textAlign: 'center',
fontSize: '16px',
borderRadius: '5px',
transition: 'all 1s'
})
setElStyle(closeBtn, {
position: 'absolute',
top: '-3px',
right: '3px',
width: '15px',
height: '15px',
zIndex: '999',
fontWeight: '800',
fontSize: '15px',
borderRadius: '5px',
cursor: 'pointer',
userSelect: 'none'
})
//控制方向
switch(config?.direction) {
case 'top': setElStyle(MessageWrap, {top: '1%', left: '50%', transform: 'translateX(-50%)'}); break;
case 'top left': setElStyle(MessageWrap, {top: '1%', left: '.5%'}); break;
case 'left': setElStyle(MessageWrap, {top: '50%', left: '1%', transform: 'translateY(-50%)'}); break;
case 'top right': setElStyle(MessageWrap, {top: '1%', right: '.5%', }); break;
case 'right': setElStyle(MessageWrap, {top: '50%', right: '.5%', transform: 'translateY(-50%)'}); break;
case 'bottom': setElStyle(MessageWrap, {bottom: '1%', left: '50%', transform: 'translateX(-50%)'}); break;
case 'bottom left': setElStyle(MessageWrap, {bottom: '1%'}); break;
case 'bottom right': setElStyle(MessageWrap, {bottom: '1%', right: '.5%'}); break;
default: setElStyle(MessageWrap, {top: '1%', left: '50%', transform: 'translateX(-50%)'}); break;
}
switch(config?.type) {
case 'success': setElStyle(MessageBox, {border: '1.5px solid rgb(225, 243, 216)', backgroundColor: 'rgb(240, 249, 235)', color: 'rgb(103, 194, 58)'}); break;
case 'warning': setElStyle(MessageBox, {border: '1.5px solid rgb(250, 236, 216)', backgroundColor: 'rgb(253, 246, 236)', color: 'rgb(230, 162, 60)'}); break;
case 'error': setElStyle(MessageBox, {border: '1.5px solid rgb(253, 226, 226)', backgroundColor: 'rgb(254, 240, 240)', color: 'rgb(245, 108, 108)'}); break;
default: setElStyle(MessageBox, {border: '1.5px solid rgba(202, 228, 255) ', backgroundColor: 'rgba(236, 245, 255)', color: 'rgb(64, 158, 255)'}); break;
}
MessageBox.appendChild(closeBtn)
let oldMessageWrap = document.querySelector('.messageWrap')
if(oldMessageWrap) {
oldMessageWrap.appendChild(MessageBox)
} else {
MessageWrap.appendChild(MessageBox)
document.body.appendChild(MessageWrap)
}
let ani = MessageBox.animate([
{
transform: "translate(0, -100%)" ,
opacity: 0.3,
},
{
transform: "translate(0, 18px)",
opacity: 0.7,
offset: 0.9,
},
{
transform: "translate(0, 15px)",
opacity: 1,
offset: 1,
},
], {
duration: 300,
fill: 'forwards',
easing: 'ease-out',
})
//控制消失
let timer = setTimeout(() => {
ani.onfinish = () => {
MessageBox.remove()
}
ani.reverse()
}, (config?.showTime || 3000))
//鼠标悬停时不清除,离开时重新计时
MessageBox.addEventListener('mouseenter', () => clearTimeout(timer))
MessageBox.addEventListener('mouseleave', () => {
timer = setTimeout(() => {
ani.reverse()
ani.onfinish = () => {
MessageBox.remove()
}
}, (config?.showTime || 3000))
})
}
/**
* @description 修改元素的css样式
* @param {Map} styleMap 样式表
* @return {void}
*/
function setElStyle(...args) {
let dataType = /\[object (.*)\]/.exec(Object.prototype.toString.call(args[0]))[1]
if (dataType === 'Map') {
const styleMap = args[0]
for (const [el, styleObj] of styleMap) {
setStyleObj(el, styleObj)
}
} else if (dataType === 'HTMLDivElement') {
const [el, styleObj] = args
setStyleObj(el, styleObj)
}
function setStyleObj(el, styleObj) {
for (let attr in styleObj) {
if (el.style[attr] !== undefined) { //检查是否存在该CSS属性
el.style[attr] = styleObj[attr]
} else {
//将key转为标准css属性名
let formatAttr = attr.replace(/[A-Z]/, (match) => `-${match.toLowerCase()}`)
console.error(el, `的 ${formatAttr} CSS属性设置失败!`)
}
}
}
}
/**
==日志===
2024/2/1
- 新增:个体属性面板自动保存
2024/2/3
- 新增:快捷键【Q】俯视角左调
- 新增:快捷键【E】俯视角右调
- 新增:快捷键【鼠标中键】进入/退出三视图编辑模式
- 新增:快捷键【空格】帧数前切(选中个体时,控制在关联帧范围;三视图总览表→批量前切)
- 新增:快捷键【`】帧数后切(选中个体时,控制在关联帧范围;三视图总览表→批量后切)
- 新增:快捷键【Ctrl + Y】 开启/关闭自动删除
=========
**/
})();