OnShape helper

Various tweaks for OnShape, such as remap F2 for rename (SHIFT + N)

  1. // ==UserScript==
  2. // @name OnShape helper
  3. // @namespace V@no
  4. // @version 25.7.24
  5. // @description Various tweaks for OnShape, such as remap F2 for rename (SHIFT + N)
  6. // @author V@no
  7. // @license MIT
  8. // @match https://cad.onshape.com/documents
  9. // @match https://cad.onshape.com/documents?*
  10. // @match https://cad.onshape.com/documents/*
  11. // @match https://cad.onshape.com/onshape-keyboard-shortcuts*
  12. // @icon https://onshape.com/favicon.png
  13. // @grant none
  14. // ==/UserScript==
  15.  
  16. (CSS =>
  17. {
  18. // eslint-disable-next-line no-unused-expressions
  19. "use strict";
  20. /*
  21. ^ = CTRL
  22. ! = ALT
  23. + = SHIFT
  24. */
  25. const VERSION = "25.7.24";
  26. const CHANGES = `Merge commit 'fa2822ed1a891cfef98a2950693768c7c81afba1'`;
  27. const map = {
  28. "F2": {key: "N", code: "KeyN", keyCode: 78, shiftKey: true}
  29. };
  30.  
  31. const elStyle = document.createElement("style");
  32. elStyle.id = "onShapeHelper";
  33. elStyle.textContent = CSS;
  34. document.head.append(elStyle);
  35.  
  36. let mouseEvent = {};
  37. document.addEventListener("mousemove", evt =>
  38. {
  39. mouseEvent = evt;
  40. }, false);
  41.  
  42. const keyboardShortcuts = {
  43. header: null,
  44. list: new Map()
  45. };
  46.  
  47. document.body.addEventListener("keydown", evt =>
  48. {
  49. let modifier = "";
  50. modifier = evt.altKey ? "!" : "";
  51. modifier = evt.shiftKey ? "+" : "";
  52. modifier = evt.ctrlKey || evt.metaKey ? "^" : "";
  53. const key = modifier + evt.code;
  54. if (!evt.isTrusted || !(key in map) || evt.altKey || evt.shiftKey || evt.ctrlKey || evt.metaKey)
  55. return;// console.log(evt, mouseEvent);
  56.  
  57. if (mouseEvent.target)
  58. {
  59. evt.target.dispatchEvent(new KeyboardEvent(evt.type, Object.assign({}, evt, {key: " ", code: "space", keyCode: 32}, {bubbles: true})));
  60. mouseEvent.target.dispatchEvent(new PointerEvent("click", mouseEvent));
  61. }
  62.  
  63. evt.target.dispatchEvent(new KeyboardEvent(evt.type, Object.assign({}, evt, map[key], {bubbles: true})));
  64. }, true);
  65.  
  66. const dataValue = (el, value) =>
  67. {
  68. el.dataset.value = value;
  69. };
  70.  
  71. const changeByRegex = /Change by (.+) at (.+)$/;
  72.  
  73. const getChildIndex = child =>
  74. {
  75. let i = 0;
  76. while ((child = child.previousElementSibling) !== null)
  77. i++;
  78. return i;
  79. };
  80. // eslint-disable-next-line no-unused-vars
  81. const observer = new MutationObserver((mutationList, _observer) =>
  82. {
  83. const types = {};
  84. for (const mutation of mutationList)
  85. {
  86. for(const node of mutation.addedNodes)
  87. {
  88. if (node.nodeType !== 1)
  89. continue;
  90.  
  91. /* ----------------------------- input boxes ----------------------------- */
  92. if (node.matches("input:not(.OSH)"))
  93. {
  94. node.classList.add("OSH");
  95. node.parentElement.classList.add("OSH", "input_box");
  96. const eventHandler = () => dataValue(node.parentElement, node.value);
  97. node.addEventListener("input", eventHandler);
  98. // inserted variables don't trigger input event, so we need to check for changes
  99. let previousValue = null;
  100. const loop = timestamp =>
  101. {
  102. if (previousValue !== node.value)
  103. {
  104. previousValue = node.value;
  105. eventHandler();
  106. }
  107. if (node.isConnected)
  108. return requestAnimationFrame(loop);
  109. };
  110. requestAnimationFrame(loop);
  111. }
  112.  
  113. /* ------------------------- version and history ------------------------- */
  114. if (node.matches(".os-flex-table-row:not(.change, .OSH, .separator)"))
  115. {
  116. node.classList.add("OSH");
  117. const elDescription = document.createElement("div");
  118. elDescription.classList.add("os-flex-col", "os-item-description", "inside-document", "OSH_description");
  119. elDescription.textContent = node.dataset.bsExpandedContent || "";
  120. node.append(elDescription);
  121. if (node.dataset.bsExpandedContent)
  122. types.historyDescription = true;
  123. }
  124. /* --------------------- version and history changes --------------------- */
  125. if (node.matches(".os-flex-table-row.change:not(.OSH)"))
  126. {
  127. node.classList.add("OSH");
  128. const changeBy = node.dataset.bsOriginalTitle.match(changeByRegex);
  129. let parentChangeBy = "";
  130. for(let i = getChildIndex(node); i >= 0; --i)
  131. {
  132. const elSibling = node.parentElement.children[i].querySelector(".os-item-modified-by");
  133. if (elSibling)
  134. {
  135. parentChangeBy = elSibling.textContent.trim();
  136. break;
  137. }
  138. }
  139. const elModified = node.querySelector(".os-flex-col.os-item-modified-date.inside-document");
  140. elModified.innerHTML = (parentChangeBy === changeBy[1] ? `` : `${changeBy[1]}\n`) + changeBy[2];
  141. node.classList.toggle("OSH_single_line", parentChangeBy === changeBy[1]);
  142. }
  143.  
  144. /* ---------------------------- version graph ---------------------------- */
  145. if (node.matches("line") && !types.versionGraph && node.closest(".os-version-graph"))
  146. {
  147. types.versionGraph = node.parentElement;
  148. }
  149.  
  150. /* ---------------------------- configuration ---------------------------- */
  151. if (!node.classList.contains(".single-table-container.os-virtual-scroll-section:not(.OSH_conf)"))
  152. {
  153. const nlNodes = node.querySelectorAll(`a:not(.OSH_conf)[ng-click="configurationTable.moveParameterUp()"], a:not(.OSH_conf)[ng-click="configurationTable.moveParameterDown()"`);
  154. if (nlNodes.length > 0)
  155. {
  156. types.configuration = nlNodes.length;
  157. node.classList.add("OSH_conf");
  158. }
  159. for(let i = 0; i < nlNodes.length; i++)
  160. {
  161. const elA = nlNodes[i];
  162. elA.classList.add("OSH_conf");
  163. const elParent = elA.closest("div.os-table-header-responsive-last-row>div.d-flex");
  164. elParent.classList.add("OSH_conf_row");
  165. if (elParent.upDown === undefined)
  166. elParent.upDown = {};
  167.  
  168. const type = elA.matches(`[ng-click="configurationTable.moveParameterUp()"]`);
  169. if (elParent.upDown[type])
  170. elParent.upDown[type].replaceWith(elA);
  171. else
  172. elParent.prepend(elA);
  173.  
  174. elParent.upDown[type] = elA;
  175. elA.classList.add(type ? "UP" : "DOWN");
  176. elA.title = elA.textContent;
  177. elA.textContent = type ? "▲" : "▼";
  178.  
  179. elA.addEventListener("click", () => moved(elA.parentElement.parentElement.parentElement));
  180. }
  181. }
  182.  
  183. /* ---------------------------- message bubble --------------------------- */
  184. if (node.matches(`div[ng-include="'/project/web/woolsthorpe/app/partials/toolbarMessageBubble.html'"]`) && node.parentElement !== document.body)
  185. {
  186. document.body.append(node);
  187. }
  188.  
  189. if (node.matches(".map-container"))
  190. {
  191. types.keyboardShortcutsTable = node;
  192. }
  193. // console.log(node);
  194. /* ----------------------- Keyboard Shortcuts panel ---------------------- */
  195. // if (node.matches("customize-shortcut-map"))
  196. // {
  197. // types.keyboardShortcutsHeader = node.querySelector(".shortcut-catagory-header-list");
  198. // }
  199. // if (node.matches("keyboard-shortcut"))
  200. // {
  201. // types.keyboardShortcuts = node.parentElement;
  202. // }
  203.  
  204. if (node.matches(".d-flex.flex-column.ng-star-inserted:not(.OSH)"))
  205. {
  206. node.classList.add("OSH");
  207. types.documentList = node;
  208. }
  209.  
  210. } // for added nodes
  211. } // for mutation list
  212.  
  213. if (types.configuration)
  214. {
  215. if (!document.querySelector("div.single-table-container.os-virtual-scroll-section:first-child .UP"))
  216. {
  217. const elRow = document.querySelector("div.single-table-container.os-virtual-scroll-section:first-child div.OSH_conf_row");
  218. const elA = elRow.firstChild.cloneNode(true);
  219. elA.setAttribute("ng-click", "configurationTable.moveParameterUp()");
  220. elA.classList.remove("DOWN");
  221. elA.title = "Move UP";
  222. elA.classList.add("OSH_conf", "UP");
  223. elA.textContent = "▲";
  224. elRow.prepend(elA);
  225. const elParent = elA.closest("div.os-table-header-responsive-last-row>div.d-flex");
  226. elParent.upDown[true] = elA;
  227.  
  228. }
  229. if (!document.querySelector("div.single-table-container.os-virtual-scroll-section:last-child .DOWN"))
  230. {
  231. const elRow = document.querySelector("div.single-table-container.os-virtual-scroll-section:last-child div.OSH_conf_row");
  232. const elA = elRow.firstChild.cloneNode(true);
  233. elA.setAttribute("ng-click", "configurationTable.moveParameterDown()");
  234. elA.classList.remove("UP");
  235. elA.title = "Move DOWN";
  236. elA.classList.add("OSH_conf", "DOWN");
  237. elA.textContent = "▼";
  238. elRow.append(elA);
  239. const elParent = elA.closest("div.os-table-header-responsive-last-row>div.d-flex");
  240. elParent.upDown[false] = elA;
  241. }
  242. }
  243.  
  244. if (types.versionGraph)
  245. {
  246. const nlLines = types.versionGraph.querySelectorAll("line");
  247. let max = 0;
  248. let min = 1e10;
  249. for(let i = 0; i < nlLines.length; i++)
  250. {
  251. const elLine = nlLines[i];
  252. max = Math.max(max, Number.parseFloat(elLine.getAttribute("x1")));
  253. min = Math.min(min, Number.parseFloat(elLine.getAttribute("x1")));
  254. }
  255. const elGraph = types.versionGraph.closest(".document-panel-main-content");
  256. elGraph.style.setProperty("--os-version-graph-width", `${max - min + 28}px`);
  257. elGraph.style.setProperty("--os-version-graph-left", `-${min - 14}px`);
  258. }
  259.  
  260. if (types.historyDescription)
  261. {
  262. document.querySelector(".versions-history-table-container").classList.add("OSH_description");
  263. }
  264.  
  265. if (types.keyboardShortcutsTable)
  266. {
  267. const node = types.keyboardShortcutsTable;
  268. const nlEmpty = node.querySelectorAll(".map-container > div");
  269. const nlList = node.querySelectorAll(".map-container > div > div");
  270. for( const elItem of nlList)
  271. {
  272. // elItem.remove();
  273. elItem.parentElement.parentElement.append(elItem);
  274. console.log(elItem);
  275. }
  276. for(const elEmpty of nlEmpty)
  277. elEmpty.remove();
  278. }
  279. // if (types.keyboardShortcutsHeader)
  280. // {
  281. // keyboardShortcuts.header = types.keyboardShortcutsHeader.firstElementChild;
  282. // }
  283.  
  284. // if (types.keyboardShortcuts && keyboardShortcuts.header)
  285. // {
  286. // const elHeader = keyboardShortcuts.header;
  287. // const type = elHeader.textContent.trim();
  288. // keyboardShortcuts.list.set(type, []);
  289. // const nlShortcuts = types.keyboardShortcuts.querySelectorAll("keyboard-shortcut");
  290. // for (const elShortcut of nlShortcuts)
  291. // {
  292. // const list = keyboardShortcuts.list.get(type);
  293. // list.push(elShortcut.cloneNode(true));
  294. // }
  295. // if (elHeader.nextElementSibling)
  296. // {
  297. // keyboardShortcuts.header = elHeader.nextElementSibling;
  298. // keyboardShortcuts.header.click();
  299. // }
  300. // else
  301. // {
  302. // keyboardShortcuts.header = null;
  303. // elHeader.parentNode.firstElementChild.click();
  304. // keyboardShortcutsShow();
  305. // }
  306. // }
  307.  
  308. /* --------------- prevent document folder open in a new tab --------------- */
  309. const elFolder = document.querySelector("a.folder[target='_blank']");
  310. if (elFolder)
  311. elFolder.removeAttribute("target");
  312.  
  313. // if (types.documentList)
  314. // {
  315. // const node = types.documentList;
  316. // const elSplitter = node.querySelector("osx-splitter");
  317. // const saveStyle = () => localStorage.setItem("OSH_splitterStyle", elSplitter.getAttribute("style"));
  318. // let timer = null;
  319. // const mutationObserver2 = new MutationObserver(mutationList2 =>
  320. // {
  321. // if (!elSplitter.classList.contains("OSH"))
  322. // {
  323. // elSplitter.classList.add("OSH");
  324. // const savedStyle = localStorage.getItem("OSH_splitterStyle");
  325. // if (savedStyle)
  326. // {
  327. // elSplitter.setAttribute("style", savedStyle);
  328. // elSplitter.querySelector(".cdk-drag.gutter-handle").dispatchEvent(new Event("dragstart", {bubbles: true}));
  329. // return;
  330. // }
  331.  
  332. // }
  333. // clearTimeout(timer);
  334. // timer = setTimeout(saveStyle, 500);
  335. // console.log("OSH: MutationObserver2", mutationList2);
  336. // });
  337. // mutationObserver2.observe(elSplitter, { attributeFilter: ["style"], attributeOldValue: true });
  338.  
  339. // }
  340.  
  341. });
  342.  
  343. const keyboardShortcutsShow = () =>
  344. {
  345. const containerId = "OSH_keyboardShortcutsTable";
  346. let container = document.getElementById(containerId);
  347. if (!container)
  348. {
  349. container = document.createElement("div");
  350. container.id = containerId;
  351. document.body.append(container);
  352. }
  353.  
  354. // Clear previous content
  355. container.innerHTML = "";
  356.  
  357. // Create table
  358. const table = document.createElement("table");
  359. // table.style = "border-collapse:collapse;width:100%;";
  360. const thead = document.createElement("thead");
  361. const trHead = document.createElement("tr");
  362.  
  363. // Collect all unique shortcut property names for columns
  364. const allKeys = [...keyboardShortcuts.list.keys()];
  365. for (const key of allKeys)
  366. {
  367. const th = document.createElement("th");
  368. th.textContent = key;
  369. th.colSpan = 2;
  370. // th.style = "border:1px solid #ccc;padding:0.5em;background:#eee;";
  371. trHead.append(th);
  372. }
  373. thead.append(trHead);
  374. table.append(thead);
  375.  
  376. // Find the max number of shortcuts in any column
  377. const maxRows = Math.max(...Array.from(keyboardShortcuts.list.values(), v => v.length));
  378.  
  379. const tbody = document.createElement("tbody");
  380. for (let row = 0; row < maxRows; row++)
  381. {
  382. const tr = document.createElement("tr");
  383.  
  384. for (const key of allKeys)
  385. {
  386. // td.style = "border:1px solid #ccc;padding:0.5em;";
  387. const shortcut = keyboardShortcuts.list.get(key)[row];
  388. const td1 = document.createElement("td");
  389. const td2 = td1.cloneNode();
  390. if (shortcut)
  391. {
  392. td1.append(shortcut.querySelector(".shortcut-keys"));
  393. td2.append(shortcut.querySelector(".shortcut-label"));
  394. }
  395. tr.append(td1);
  396. tr.append(td2);
  397. }
  398. tbody.append(tr);
  399. }
  400. table.append(tbody);
  401.  
  402. // Add close button
  403. const buttonClose = document.createElement("button");
  404. buttonClose.textContent = "Close";
  405. buttonClose.style = "position:absolute;top:0.5em;right:0.5em;";
  406. buttonClose.addEventListener("click", () => container.remove());
  407.  
  408. container.append(table);
  409. container.append(buttonClose);
  410. };
  411.  
  412. observer.observe(document.body, {
  413. childList: true,
  414. subtree: true,
  415. });
  416.  
  417. const moved = el =>
  418. {
  419. moved.clear();
  420. el.classList.add("moved");
  421. moved.el = el;
  422. moved.timer = setTimeout(moved.clear, 2000);
  423.  
  424. };
  425.  
  426. moved.clear = () =>
  427. {
  428. clearTimeout(moved.timer);
  429. if (moved.el)
  430. {
  431. moved.el.classList.remove("moved");
  432. moved.el = null;
  433. }
  434. };
  435. console.log(`OnShape helper v${VERSION} loaded`, "https://greasyfork.org/en/scripts/522636");
  436. })(`
  437. .dimension-edit-container .ns-feature-parameter .bti-numeric-text,
  438. .dimension-edit-container os-quantity-parameter input,
  439. .dimension-edit
  440. {
  441. max-width: unset;
  442. z-index: 9999;
  443. text-align: center;
  444. }
  445.  
  446. div.input_box.OSH::before,
  447. div.input_box.OSH::after {
  448. box-sizing: border-box;
  449. }
  450.  
  451. div.input_box.OSH {
  452. display: inline-grid;
  453. vertical-align: top;
  454. align-items: center;
  455. position: relative;
  456. }
  457.  
  458. div.input_box.OSH::after,
  459. div.input_box.OSH input
  460. {
  461. width: auto;
  462. min-width: 1em;
  463. grid-area: 1/2;
  464. font: inherit;
  465. padding: 0 0.25em 0 0;
  466. margin: 0;
  467. resize: none;
  468. background: none;
  469. -webkit-appearance: none;
  470. -moz-appearance: none;
  471. appearance: none;
  472. border: none;
  473. }
  474.  
  475. /* --- this will force to extend the width of the input to fit the content -- */
  476. div.input_box.OSH::after {
  477. content: attr(data-value) " ";
  478. visibility: hidden;
  479. white-space: pre-wrap;
  480. }
  481.  
  482. div.OSH_conf_row > .OSH_conf {
  483. font-size: x-large;
  484. padding: 0 0.2em;
  485. line-height: 1em;
  486. }
  487.  
  488. div.OSH_conf_row > .OSH_conf:hover {
  489. background-color: var(--os-table-cell-fill--hover);
  490. }
  491.  
  492. div.OSH_conf_row > .OSH_conf.UP {
  493. order: 1;
  494. }
  495.  
  496. div.OSH_conf_row > .OSH_conf.DOWN {
  497. order: 2;
  498. }
  499.  
  500. div.OSH_conf_row > :not(.OSH_conf) {
  501. order: 3;
  502. }
  503.  
  504. div.moved {
  505. background-color: var(--os-alert-background-success);
  506. }
  507.  
  508. div.single-table-container.os-virtual-scroll-section:first-child .UP,
  509. div.single-table-container.os-virtual-scroll-section:last-child .DOWN {
  510. opacity: 0.5;
  511. pointer-events: none;
  512. }
  513.  
  514. /* --------------------- Message bubble move to the top --------------------- */
  515. os-message-bubble .os-message-bubble-container.document-message-bubble {
  516. top: 5px;
  517. }
  518. .os-speech-bubble-container
  519. {
  520. top: 0;
  521. }
  522.  
  523. /* ----------------------------- version history ---------------------------- */
  524. .versions-history-table-container .os-flex-col.os-item-workspace-or-version-graph.inside-document {
  525. flex: initial !important;
  526. }
  527.  
  528. /* -------------------------- version history graph ------------------------- */
  529. .versions-history-table-container .os-flex-col.os-item-workspace-or-version-graph.inside-document {
  530. min-width: var(--os-version-graph-width, 140px);
  531. }
  532. .os-version-graph > svg {
  533. margin-left: var(--os-version-graph-left, 0);
  534. }
  535.  
  536. /* ------------ version history search result header modified by ------------ */
  537. .versions-history-table-container .os-flex-col.history-search-results-header:last-child,
  538. .versions-history-table-container .os-flex-table-row.history-search-result .os-flex-col:not(.os-item-workspace-or-version-actions).os-item-modified-date,
  539. /* -------------------------- version history user -------------------------- */
  540. .versions-history-table-container .os-flex-col.os-item-modified-by-and-date.inside-document,
  541. /* ---------------------- version history modified date --------------------- */
  542. .os-flex-col.os-item-modified-date.inside-document,
  543. /* ----------------------- version history description ---------------------- */
  544. .versions-history-table-container.OSH_description .os-flex-col.history-search-results-header,
  545. .versions-history-table-container.OSH_description .os-flex-col.os-item-description{
  546. flex: none;
  547. }
  548.  
  549. /* ----------------------- version history description ---------------------- */
  550. .versions-history-table-container .os-flex-col.os-item-modified-date.inside-document,
  551. .versions-history-table-container .os-flex-col.os-item-workspace-or-version-name.inside-document,
  552. .versions-history-table-container .os-flex-col.os-item-workspace-or-version-description.inside-document {
  553. max-width: unset;
  554. }
  555.  
  556. /* ----------------------- version history change time ---------------------- */
  557. .os-flex-col.os-item-modified-date.inside-document {
  558. font-size: 0.8em;
  559. white-space: pre;
  560. line-height: 1em;
  561. text-align: end;
  562. max-width: 10em !important;
  563. text-overflow: ellipsis;
  564. overflow: hidden;
  565. padding-top: 0.3em;
  566. }
  567. .OSH_single_line > .os-flex-col.os-item-modified-date.inside-document {
  568. padding-top: 0.9em;
  569. }
  570.  
  571. /* ------------------- version history description column ------------------- */
  572. .versions-history-table-container:not(.OSH_description) .OSH_description {
  573. display: none !important;
  574. }
  575. .versions-history-table-container.OSH_description .os-flex-col.os-item-modified-by-and-date.inside-document + .ng-hide,
  576. .versions-history-table-container.OSH_description .os-flex-col.os-item-description {
  577. display: block !important;
  578. order: 3;
  579. }
  580. .versions-history-table-container.OSH_description .os-item-modified-by-and-date {
  581. order: 4;
  582. }
  583.  
  584. .versions-history-table-container.OSH_description .os-item-workspace-or-version-name {
  585. order: 2;
  586. }
  587. .versions-history-table-container.OSH_description .os-item-workspace-or-version-graph:not(.change-item) {
  588. order: 1;
  589. }
  590.  
  591. /* --------------------------- Keyboard shortcuts --------------------------- */
  592. #OSH_keyboardShortcutsTable {
  593. position:fixed;
  594. top:10%;
  595. left:10%;
  596. background:var(--background-color);
  597. z-index:9999;
  598. padding:1em;
  599. border:2px solid #888;
  600. max-height:80vh;
  601. overflow:auto;
  602. }
  603. #OSH_keyboardShortcutsTable th {
  604. text-align: center;
  605. border-bottom: 1px solid var(--text-color);
  606. padding: 0.5em 0;
  607. }
  608. #OSH_keyboardShortcutsTable > table {
  609. border: none;
  610. border-collapse: collapse;
  611. }
  612. #OSH_keyboardShortcutsTable > table th,
  613. #OSH_keyboardShortcutsTable > table td:nth-child(odd) {
  614. border-left: 1px solid var(--text-color);
  615. }
  616. #OSH_keyboardShortcutsTable > table th:first-child,
  617. #OSH_keyboardShortcutsTable > table td:first-child {
  618. border-left: none;
  619. }
  620. #OSH_keyboardShortcutsTable .keyboard-shortcut-container,
  621. #OSH_keyboardShortcutsTable .keyboard-shortcut-container .shortcut-keys span {
  622. margin: 0;
  623. }
  624.  
  625. #OSH_keyboardShortcutsTable .shortcut-keys {
  626. display: flex;
  627. flex-wrap: nowrap;
  628. justify-content: right;
  629. }
  630.  
  631. /* just a visual indicator that script is running - a green dot on the logo */
  632. osx-navbar-logo-component > a {
  633. position: relative;
  634. }
  635. osx-navbar-logo-component > a::before {
  636. content: "";
  637. position: absolute;
  638. background-color: green;
  639. left: 12px;
  640. top: 18px;
  641. font-size: 2em;
  642. width: 5px;
  643. height: 5px;
  644. border-radius: 100%;
  645. }
  646. `);