Greasy Fork 还支持 简体中文。

Angular Component Modifier

Permite modificar propiedades de componentes en Angular en un servidor de desarrollo con funcionalidad para guardar y restaurar valores

目前為 2025-03-19 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Angular Component Modifier
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.1
  5. // @description Permite modificar propiedades de componentes en Angular en un servidor de desarrollo con funcionalidad para guardar y restaurar valores
  6. // @author Blas Santomé Ocampo
  7. // @match http://localhost:*/*
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. "use strict";
  14.  
  15. const IGNORED_PROPERTIES = ["__ngContext__"];
  16.  
  17. const STORAGE_KEY = "angularModifier_savedStates";
  18. let savedStates = {};
  19.  
  20. try {
  21. const storedStates = localStorage.getItem(STORAGE_KEY);
  22. if (storedStates) {
  23. savedStates = JSON.parse(storedStates);
  24. }
  25. } catch (err) {
  26. console.warn("[Angular Modifier] Error al cargar estados guardados:", err);
  27. }
  28. console.log(
  29. "[Angular Modifier] UserScript cargado. Usa OPTION (⌥) + Click en un componente app-*."
  30. );
  31.  
  32. document.addEventListener(
  33. "click",
  34. function (event) {
  35. if (!event.altKey) return;
  36. event.preventDefault();
  37.  
  38. let ng = window.ng;
  39. if (!ng) {
  40. alert(
  41. "⚠️ Angular DevTools no está disponible. Asegúrate de estar en un servidor de desarrollo."
  42. );
  43. return;
  44. }
  45.  
  46. let el = event.target;
  47. let component = null;
  48. let componentName = "Componente Desconocido";
  49. let componentId = "";
  50.  
  51. while (el) {
  52. component = ng.getComponent(el);
  53. if (component && el.tagName.toLowerCase().startsWith("app-")) {
  54. componentName = el.tagName.toLowerCase();
  55. componentId = generateComponentId(el, componentName);
  56. break;
  57. }
  58. el = el.parentElement;
  59. }
  60.  
  61. if (!component) {
  62. alert(
  63. "⚠️ No se encontró un componente Angular válido (app-*) en la jerarquía."
  64. );
  65. return;
  66. }
  67.  
  68. console.log(
  69. `[Angular Modifier] Componente seleccionado: ${componentName} (ID: ${componentId})`,
  70. component
  71. );
  72.  
  73. showComponentEditor(component, componentName, componentId);
  74. },
  75. true
  76. );
  77.  
  78. function generateComponentId(element, componentName) {
  79. let path = [];
  80. let current = element;
  81. while (current && current !== document.body) {
  82. let index = 0;
  83. let sibling = current;
  84. while ((sibling = sibling.previousElementSibling)) {
  85. index++;
  86. }
  87. path.unshift(index);
  88. current = current.parentElement;
  89. }
  90. return `${componentName}_${path.join("_")}`;
  91. }
  92.  
  93. function showComponentEditor(component, componentName, componentId) {
  94. let modal = document.createElement("div");
  95. modal.style.position = "fixed";
  96. modal.style.top = "50%";
  97. modal.style.left = "50%";
  98. modal.style.transform = "translate(-50%, -50%)";
  99. modal.style.background = "white";
  100. modal.style.padding = "20px";
  101. modal.style.boxShadow = "0px 0px 10px rgba(0,0,0,0.2)";
  102. modal.style.zIndex = "10000";
  103. modal.style.borderRadius = "8px";
  104. modal.style.width = "400px";
  105. modal.style.maxHeight = "500px";
  106. modal.style.overflowY = "auto";
  107.  
  108. let title = document.createElement("h3");
  109. title.innerText = componentName;
  110. title.style.marginTop = "0";
  111. modal.appendChild(title);
  112.  
  113. let form = document.createElement("form");
  114.  
  115. let formGroups = {};
  116. let editableProps = {};
  117.  
  118. Object.keys(component).forEach((prop) => {
  119. if (IGNORED_PROPERTIES.includes(prop)) return;
  120.  
  121. let value = component[prop];
  122.  
  123. if (typeof value === "function") return;
  124.  
  125. if (
  126. value &&
  127. typeof value === "object" &&
  128. value.constructor.name === "FormGroup"
  129. ) {
  130. formGroups[prop] = value;
  131. appendFormGroupFields(form, value, prop);
  132. return;
  133. }
  134.  
  135. if (value !== null && typeof value === "object") return;
  136.  
  137. let input = appendEditableField(form, component, prop, value);
  138. if (input) {
  139. editableProps[prop] = {
  140. type: typeof value,
  141. input: input,
  142. };
  143. }
  144. });
  145.  
  146. modal.appendChild(form);
  147.  
  148. let stateManagementDiv = document.createElement("div");
  149. stateManagementDiv.style.marginTop = "15px";
  150. stateManagementDiv.style.borderTop = "1px solid #eee";
  151. stateManagementDiv.style.paddingTop = "10px";
  152.  
  153. let saveStateButton = document.createElement("button");
  154. saveStateButton.innerText = "Guardar Estado Actual";
  155. saveStateButton.style.padding = "5px 10px";
  156. saveStateButton.style.marginRight = "10px";
  157. saveStateButton.style.background = "#28a745";
  158. saveStateButton.style.color = "white";
  159. saveStateButton.style.border = "none";
  160. saveStateButton.style.borderRadius = "5px";
  161. saveStateButton.style.cursor = "pointer";
  162. saveStateButton.addEventListener("click", (e) => {
  163. e.preventDefault();
  164. saveCurrentState(component, componentId, formGroups, editableProps);
  165. });
  166. stateManagementDiv.appendChild(saveStateButton);
  167.  
  168. let restoreStateButton = document.createElement("button");
  169. restoreStateButton.innerText = "Restaurar Estado";
  170. restoreStateButton.style.padding = "5px 10px";
  171. restoreStateButton.style.background = "#007bff";
  172. restoreStateButton.style.color = "white";
  173. restoreStateButton.style.border = "none";
  174. restoreStateButton.style.borderRadius = "5px";
  175. restoreStateButton.style.cursor = "pointer";
  176.  
  177. if (!savedStates[componentId]) {
  178. restoreStateButton.disabled = true;
  179. restoreStateButton.style.opacity = "0.5";
  180. restoreStateButton.style.cursor = "not-allowed";
  181. }
  182.  
  183. restoreStateButton.addEventListener("click", (e) => {
  184. e.preventDefault();
  185. restoreSavedState(component, componentId, formGroups, editableProps);
  186. });
  187. stateManagementDiv.appendChild(restoreStateButton);
  188.  
  189. modal.appendChild(stateManagementDiv);
  190.  
  191. let fileLabel = document.createElement("label");
  192. fileLabel.innerText = "Cargar JSON:";
  193. fileLabel.style.display = "block";
  194. fileLabel.style.marginTop = "15px";
  195. modal.appendChild(fileLabel);
  196.  
  197. let fileInput = document.createElement("input");
  198. fileInput.type = "file";
  199. fileInput.accept = "application/json";
  200. fileInput.style.marginTop = "5px";
  201. fileInput.style.width = "100%";
  202. fileInput.addEventListener("change", (event) =>
  203. handleFileUpload(event, formGroups)
  204. );
  205. modal.appendChild(fileInput);
  206.  
  207. let exportButton = document.createElement("button");
  208. exportButton.innerText = "Exportar a JSON";
  209. exportButton.style.marginTop = "10px";
  210. exportButton.style.width = "100%";
  211. exportButton.style.padding = "5px";
  212. exportButton.style.background = "#17a2b8";
  213. exportButton.style.color = "white";
  214. exportButton.style.border = "none";
  215. exportButton.style.borderRadius = "5px";
  216. exportButton.style.cursor = "pointer";
  217. exportButton.addEventListener("click", (e) => {
  218. e.preventDefault();
  219. exportToJson(component, formGroups);
  220. });
  221. modal.appendChild(exportButton);
  222.  
  223. let closeButton = document.createElement("button");
  224. closeButton.innerText = "Cerrar";
  225. closeButton.style.marginTop = "10px";
  226. closeButton.style.width = "100%";
  227. closeButton.style.padding = "5px";
  228. closeButton.style.background = "#d9534f";
  229. closeButton.style.color = "white";
  230. closeButton.style.border = "none";
  231. closeButton.style.borderRadius = "5px";
  232. closeButton.style.cursor = "pointer";
  233.  
  234. closeButton.addEventListener("click", () => {
  235. document.body.removeChild(modal);
  236. });
  237.  
  238. modal.appendChild(closeButton);
  239. document.body.appendChild(modal);
  240. }
  241.  
  242. function appendEditableField(form, component, prop, value) {
  243. let label = document.createElement("label");
  244. label.innerText = prop;
  245. label.style.display = "block";
  246. label.style.marginTop = "5px";
  247.  
  248. let input = document.createElement("input");
  249. input.style.width = "100%";
  250. input.style.marginTop = "2px";
  251. input.dataset.propName = prop;
  252.  
  253. if (typeof value === "boolean") {
  254. input.type = "checkbox";
  255. input.checked = value;
  256. } else if (typeof value === "number") {
  257. input.type = "number";
  258. input.value = value;
  259. } else if (typeof value === "string") {
  260. input.type = "text";
  261. input.value = value;
  262. } else {
  263. return null;
  264. }
  265.  
  266. input.addEventListener("change", () => {
  267. try {
  268. if (input.type === "checkbox") {
  269. component[prop] = input.checked;
  270. } else if (input.type === "number") {
  271. component[prop] = parseFloat(input.value);
  272. } else {
  273. component[prop] = input.value;
  274. }
  275. if (typeof ng.applyChanges === "function") {
  276. ng.applyChanges(component);
  277. console.log(`[Angular Modifier] Se aplicaron cambios en ${prop}`);
  278. }
  279. } catch (err) {
  280. alert(`⚠️ Error al actualizar '${prop}': ${err.message}`);
  281. }
  282. });
  283.  
  284. form.appendChild(label);
  285. form.appendChild(input);
  286. return input;
  287. }
  288.  
  289. function appendFormGroupFields(form, formGroup, formGroupName) {
  290. let formGroupTitle = document.createElement("h4");
  291. formGroupTitle.innerText = `Formulario: ${formGroupName}`;
  292. formGroupTitle.style.marginTop = "10px";
  293. formGroupTitle.style.color = "#007bff";
  294. form.appendChild(formGroupTitle);
  295.  
  296. if (formGroup.controls) {
  297. Object.keys(formGroup.controls).forEach((controlName) => {
  298. try {
  299. const control = formGroup.controls[controlName];
  300. const currentValue = control.value;
  301.  
  302. let controlLabel = document.createElement("label");
  303. controlLabel.innerText = controlName;
  304. controlLabel.style.display = "block";
  305. controlLabel.style.marginTop = "5px";
  306. controlLabel.style.marginLeft = "10px";
  307.  
  308. let controlInput = document.createElement("input");
  309. controlInput.style.width = "95%";
  310. controlInput.style.marginTop = "2px";
  311. controlInput.style.marginLeft = "10px";
  312. controlInput.dataset.formGroup = formGroupName;
  313. controlInput.dataset.controlName = controlName;
  314.  
  315. if (typeof currentValue === "boolean") {
  316. controlInput.type = "checkbox";
  317. controlInput.checked = currentValue;
  318. } else if (typeof currentValue === "number") {
  319. controlInput.type = "number";
  320. controlInput.value = currentValue;
  321. } else {
  322. controlInput.type = "text";
  323. controlInput.value =
  324. currentValue !== null && currentValue !== undefined
  325. ? currentValue
  326. : "";
  327. }
  328.  
  329. controlInput.addEventListener("change", () => {
  330. try {
  331. let newValue;
  332. if (controlInput.type === "checkbox") {
  333. newValue = controlInput.checked;
  334. } else if (controlInput.type === "number") {
  335. newValue = parseFloat(controlInput.value);
  336. } else {
  337. newValue = controlInput.value;
  338. }
  339.  
  340. control.setValue(newValue);
  341. console.log(
  342. `[Angular Modifier] Actualizado control '${controlName}' en FormGroup '${formGroupName}'`
  343. );
  344. } catch (err) {
  345. alert(
  346. `⚠️ Error al actualizar control '${controlName}': ${err.message}`
  347. );
  348. }
  349. });
  350.  
  351. form.appendChild(controlLabel);
  352. form.appendChild(controlInput);
  353. } catch (err) {
  354. console.warn(
  355. `[Angular Modifier] Error al mostrar control '${controlName}':`,
  356. err
  357. );
  358. }
  359. });
  360. }
  361. }
  362.  
  363. function handleFileUpload(event, formGroups) {
  364. let file = event.target.files[0];
  365. if (!file) return;
  366.  
  367. let reader = new FileReader();
  368. reader.onload = function (event) {
  369. try {
  370. let jsonData = JSON.parse(event.target.result);
  371. applyJsonToForm(jsonData, formGroups);
  372. } catch (err) {
  373. alert("⚠️ Error al cargar JSON: " + err.message);
  374. }
  375. };
  376. reader.readAsText(file);
  377. }
  378.  
  379. function applyJsonToForm(jsonData, formGroups) {
  380. if (jsonData.properties) {
  381. Object.keys(jsonData.properties).forEach((prop) => {
  382. if (IGNORED_PROPERTIES.includes(prop)) return;
  383.  
  384. try {
  385. const inputElement = document.querySelector(
  386. `input[data-prop-name="${prop}"]`
  387. );
  388. if (inputElement) {
  389. if (inputElement.type === "checkbox") {
  390. inputElement.checked = jsonData.properties[prop];
  391. } else {
  392. inputElement.value = jsonData.properties[prop];
  393. }
  394. inputElement.dispatchEvent(new Event("change"));
  395. }
  396. } catch (err) {
  397. console.warn(
  398. `[Angular Modifier] Error al aplicar propiedad '${prop}':`,
  399. err
  400. );
  401. }
  402. });
  403. }
  404.  
  405. if (jsonData.formGroups) {
  406. Object.keys(jsonData.formGroups).forEach((groupName) => {
  407. if (formGroups[groupName]) {
  408. let formGroup = formGroups[groupName];
  409. const groupData = jsonData.formGroups[groupName];
  410.  
  411. Object.keys(groupData).forEach((controlName) => {
  412. if (formGroup.controls[controlName]) {
  413. try {
  414. formGroup.controls[controlName].setValue(
  415. groupData[controlName]
  416. );
  417.  
  418. const controlInput = document.querySelector(
  419. `input[data-form-group="${groupName}"][data-control-name="${controlName}"]`
  420. );
  421. if (controlInput) {
  422. if (controlInput.type === "checkbox") {
  423. controlInput.checked = groupData[controlName];
  424. } else {
  425. controlInput.value = groupData[controlName];
  426. }
  427. }
  428.  
  429. console.log(
  430. `[Angular Modifier] Campo '${controlName}' de '${groupName}' actualizado`
  431. );
  432. } catch (err) {
  433. console.warn(
  434. `[Angular Modifier] Error al actualizar control '${controlName}':`,
  435. err
  436. );
  437. }
  438. }
  439. });
  440. }
  441. });
  442. }
  443. }
  444.  
  445. function exportToJson(component, formGroups) {
  446. let exportData = {
  447. properties: {},
  448. formGroups: {},
  449. };
  450.  
  451. Object.keys(component).forEach((prop) => {
  452. if (IGNORED_PROPERTIES.includes(prop)) return;
  453.  
  454. let value = component[prop];
  455. if (
  456. typeof value !== "function" &&
  457. value !== null &&
  458. typeof value !== "object"
  459. ) {
  460. exportData.properties[prop] = value;
  461. }
  462. });
  463.  
  464. Object.keys(formGroups).forEach((groupName) => {
  465. const formGroup = formGroups[groupName];
  466. exportData.formGroups[groupName] = {};
  467.  
  468. if (formGroup.controls) {
  469. Object.keys(formGroup.controls).forEach((controlName) => {
  470. try {
  471. exportData.formGroups[groupName][controlName] =
  472. formGroup.controls[controlName].value;
  473. } catch (err) {
  474. console.warn(
  475. `[Angular Modifier] Error al exportar control '${controlName}':`,
  476. err
  477. );
  478. }
  479. });
  480. }
  481. });
  482.  
  483. const jsonString = JSON.stringify(exportData, null, 2);
  484. const blob = new Blob([jsonString], { type: "application/json" });
  485. const url = URL.createObjectURL(blob);
  486.  
  487. const a = document.createElement("a");
  488. a.href = url;
  489. a.download = `angular-component-${Date.now()}.json`;
  490. document.body.appendChild(a);
  491. a.click();
  492. document.body.removeChild(a);
  493. URL.revokeObjectURL(url);
  494. }
  495.  
  496. function saveCurrentState(component, componentId, formGroups, editableProps) {
  497. let state = {
  498. properties: {},
  499. formGroups: {},
  500. };
  501.  
  502. Object.keys(editableProps).forEach((prop) => {
  503. if (IGNORED_PROPERTIES.includes(prop)) return;
  504.  
  505. const input = editableProps[prop].input;
  506. if (input.type === "checkbox") {
  507. state.properties[prop] = input.checked;
  508. } else if (input.type === "number") {
  509. state.properties[prop] = parseFloat(input.value);
  510. } else {
  511. state.properties[prop] = input.value;
  512. }
  513. });
  514.  
  515. Object.keys(formGroups).forEach((groupName) => {
  516. const formGroup = formGroups[groupName];
  517. state.formGroups[groupName] = {};
  518.  
  519. if (formGroup.controls) {
  520. Object.keys(formGroup.controls).forEach((controlName) => {
  521. try {
  522. state.formGroups[groupName][controlName] =
  523. formGroup.controls[controlName].value;
  524. } catch (err) {
  525. console.warn(
  526. `[Angular Modifier] Error al guardar control '${controlName}':`,
  527. err
  528. );
  529. }
  530. });
  531. }
  532. });
  533.  
  534. savedStates[componentId] = state;
  535.  
  536. try {
  537. localStorage.setItem(STORAGE_KEY, JSON.stringify(savedStates));
  538. alert(`✅ Estado guardado correctamente para ${componentId}`);
  539. } catch (err) {
  540. console.error("[Angular Modifier] Error al guardar estado:", err);
  541. alert("⚠️ Error al guardar estado: " + err.message);
  542. }
  543. }
  544.  
  545. function restoreSavedState(
  546. component,
  547. componentId,
  548. formGroups,
  549. editableProps
  550. ) {
  551. const savedState = savedStates[componentId];
  552. if (!savedState) {
  553. alert("⚠️ No hay estado guardado para este componente");
  554. return;
  555. }
  556.  
  557. if (savedState.properties) {
  558. Object.keys(savedState.properties).forEach((prop) => {
  559. if (IGNORED_PROPERTIES.includes(prop)) return;
  560.  
  561. if (editableProps[prop]) {
  562. const input = editableProps[prop].input;
  563. const value = savedState.properties[prop];
  564.  
  565. if (input.type === "checkbox") {
  566. input.checked = value;
  567. } else {
  568. input.value = value;
  569. }
  570.  
  571. try {
  572. component[prop] = value;
  573. } catch (err) {
  574. console.warn(
  575. `[Angular Modifier] Error al restaurar propiedad '${prop}':`,
  576. err
  577. );
  578. }
  579. }
  580. });
  581. }
  582.  
  583. if (savedState.formGroups) {
  584. Object.keys(savedState.formGroups).forEach((groupName) => {
  585. if (formGroups[groupName]) {
  586. const formGroup = formGroups[groupName];
  587. const groupData = savedState.formGroups[groupName];
  588.  
  589. Object.keys(groupData).forEach((controlName) => {
  590. if (formGroup.controls[controlName]) {
  591. try {
  592. formGroup.controls[controlName].setValue(
  593. groupData[controlName]
  594. );
  595.  
  596. const controlInput = document.querySelector(
  597. `input[data-form-group="${groupName}"][data-control-name="${controlName}"]`
  598. );
  599. if (controlInput) {
  600. if (controlInput.type === "checkbox") {
  601. controlInput.checked = groupData[controlName];
  602. } else {
  603. controlInput.value = groupData[controlName];
  604. }
  605. }
  606. } catch (err) {
  607. console.warn(
  608. `[Angular Modifier] Error al restaurar control '${controlName}':`,
  609. err
  610. );
  611. }
  612. }
  613. });
  614. }
  615. });
  616. }
  617.  
  618. if (typeof ng.applyChanges === "function") {
  619. ng.applyChanges(component);
  620. console.log(`[Angular Modifier] Se restauró el estado del componente`);
  621. }
  622.  
  623. alert(`✅ Estado restaurado correctamente para ${componentId}`);
  624. }
  625. })();