Angular Component Modifier

Permite modificar propiedades de componentes en Angular en un servidor de desarrollo con funcionalidad para guardar y restaurar valores y navegar a componentes padre, incluye soporte para signals

  1. // ==UserScript==
  2. // @name Angular Component Modifier
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.4
  5. // @description Permite modificar propiedades de componentes en Angular en un servidor de desarrollo con funcionalidad para guardar y restaurar valores y navegar a componentes padre, incluye soporte para signals
  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. let currentElement = null;
  21.  
  22. try {
  23. const storedStates = localStorage.getItem(STORAGE_KEY);
  24. if (storedStates) {
  25. savedStates = JSON.parse(storedStates);
  26. }
  27. } catch (err) {
  28. console.warn("[Angular Modifier] Error al cargar estados guardados:", err);
  29. }
  30. console.log(
  31. "[Angular Modifier] UserScript cargado. Usa OPTION (⌥) + Click en un componente app-*."
  32. );
  33.  
  34. document.addEventListener(
  35. "click",
  36. function (event) {
  37. if (!event.altKey) return;
  38. event.preventDefault();
  39.  
  40. let ng = window.ng;
  41. if (!ng) {
  42. alert(
  43. "⚠️ Angular DevTools no está disponible. Asegúrate de estar en un servidor de desarrollo."
  44. );
  45. return;
  46. }
  47.  
  48. let el = event.target;
  49. let component = null;
  50. let componentName = "Componente Desconocido";
  51. let componentId = "";
  52.  
  53. while (el) {
  54. component = ng.getComponent(el);
  55. if (component && el.tagName.toLowerCase().startsWith("app-")) {
  56. componentName = el.tagName.toLowerCase();
  57. componentId = generateComponentId(el, componentName);
  58. currentElement = el;
  59. break;
  60. }
  61. el = el.parentElement;
  62. }
  63.  
  64. if (!component) {
  65. alert(
  66. "⚠️ No se encontró un componente Angular válido (app-*) en la jerarquía."
  67. );
  68. return;
  69. }
  70.  
  71. console.log(
  72. `[Angular Modifier] Componente seleccionado: ${componentName} (ID: ${componentId})`,
  73. component
  74. );
  75.  
  76. showComponentEditor(component, componentName, componentId);
  77. },
  78. true
  79. );
  80.  
  81. function generateComponentId(element, componentName) {
  82. let path = [];
  83. let current = element;
  84. while (current && current !== document.body) {
  85. let index = 0;
  86. let sibling = current;
  87. while ((sibling = sibling.previousElementSibling)) {
  88. index++;
  89. }
  90. path.unshift(index);
  91. current = current.parentElement;
  92. }
  93. return `${componentName}_${path.join("_")}`;
  94. }
  95.  
  96. function navigateToParentComponent(currentEl) {
  97. let ng = window.ng;
  98. if (!ng) {
  99. alert(
  100. "⚠️ Angular DevTools no está disponible. Asegúrate de estar en un servidor de desarrollo."
  101. );
  102. return false;
  103. }
  104.  
  105. if (!currentEl) {
  106. alert("⚠️ No hay ningún componente seleccionado actualmente.");
  107. return false;
  108. }
  109.  
  110. let parentEl = currentEl.parentElement;
  111. let found = false;
  112.  
  113. while (parentEl) {
  114. if (
  115. parentEl.tagName &&
  116. parentEl.tagName.toLowerCase().startsWith("app-") &&
  117. ng.getComponent(parentEl)
  118. ) {
  119. currentElement = parentEl;
  120. const component = ng.getComponent(parentEl);
  121. const componentName = parentEl.tagName.toLowerCase();
  122. const componentId = generateComponentId(parentEl, componentName);
  123.  
  124. console.log(
  125. `[Angular Modifier] Navegando al componente padre: ${componentName} (ID: ${componentId})`,
  126. component
  127. );
  128.  
  129. const existingModal = document.querySelector(".angular-modifier-modal");
  130. if (existingModal) {
  131. document.body.removeChild(existingModal);
  132. }
  133.  
  134. showComponentEditor(component, componentName, componentId);
  135. found = true;
  136. break;
  137. }
  138. parentEl = parentEl.parentElement;
  139. }
  140.  
  141. if (!found) {
  142. alert("⚠️ No se encontró un componente padre que comience con 'app-'.");
  143. }
  144.  
  145. return found;
  146. }
  147.  
  148. function showComponentEditor(component, componentName, componentId) {
  149. let modal = document.createElement("div");
  150. modal.className = "angular-modifier-modal";
  151. modal.style.position = "fixed";
  152. modal.style.top = "50%";
  153. modal.style.left = "50%";
  154. modal.style.transform = "translate(-50%, -50%)";
  155. modal.style.background = "white";
  156. modal.style.padding = "20px";
  157. modal.style.boxShadow = "0px 0px 10px rgba(0,0,0,0.2)";
  158. modal.style.zIndex = "10000";
  159. modal.style.borderRadius = "8px";
  160. modal.style.width = "400px";
  161. modal.style.maxHeight = "500px";
  162. modal.style.overflowY = "auto";
  163.  
  164. let title = document.createElement("h3");
  165. title.innerText = componentName;
  166. title.style.marginTop = "0";
  167. modal.appendChild(title);
  168.  
  169. let form = document.createElement("form");
  170.  
  171. let formGroups = {};
  172. let editableProps = {};
  173. let signals = {};
  174.  
  175. // Add signals section title
  176. let signalsTitle = document.createElement("h4");
  177. signalsTitle.innerText = "Signals";
  178. signalsTitle.style.marginTop = "15px";
  179. signalsTitle.style.color = "#007bff";
  180. signalsTitle.style.display = "none"; // Hide initially, show only if signals are found
  181. form.appendChild(signalsTitle);
  182.  
  183. // Separate div for signals
  184. let signalsDiv = document.createElement("div");
  185. signalsDiv.style.marginLeft = "10px";
  186. form.appendChild(signalsDiv);
  187.  
  188. Object.keys(component).forEach((prop) => {
  189. if (IGNORED_PROPERTIES.includes(prop)) return;
  190.  
  191. let value = component[prop];
  192.  
  193. if (typeof value === "function") {
  194. // Check if this is a signal (signals are functions with specific properties)
  195. if (isSignal(value)) {
  196. signalsTitle.style.display = "block"; // Show signals section title
  197. appendSignalField(signalsDiv, component, prop, value);
  198. signals[prop] = value;
  199. return;
  200. }
  201. return;
  202. }
  203.  
  204. if (
  205. value &&
  206. typeof value === "object" &&
  207. value.constructor.name === "FormGroup"
  208. ) {
  209. formGroups[prop] = value;
  210. appendFormGroupFields(form, value, prop);
  211. return;
  212. }
  213.  
  214. if (value !== null && typeof value === "object") return;
  215.  
  216. let input = appendEditableField(form, component, prop, value);
  217. if (input) {
  218. editableProps[prop] = {
  219. type: typeof value,
  220. input: input,
  221. };
  222. }
  223. });
  224.  
  225. modal.appendChild(form);
  226.  
  227. let parentComponentButton = document.createElement("button");
  228. parentComponentButton.innerText = "Ir al Componente Padre";
  229. parentComponentButton.style.marginTop = "15px";
  230. parentComponentButton.style.width = "100%";
  231. parentComponentButton.style.padding = "8px";
  232. parentComponentButton.style.background = "#ffc107";
  233. parentComponentButton.style.color = "black";
  234. parentComponentButton.style.border = "none";
  235. parentComponentButton.style.borderRadius = "5px";
  236. parentComponentButton.style.cursor = "pointer";
  237. parentComponentButton.style.fontWeight = "bold";
  238.  
  239. parentComponentButton.addEventListener("click", (e) => {
  240. e.preventDefault();
  241. navigateToParentComponent(currentElement);
  242. });
  243.  
  244. modal.appendChild(parentComponentButton);
  245.  
  246. let stateManagementDiv = document.createElement("div");
  247. stateManagementDiv.style.marginTop = "15px";
  248. stateManagementDiv.style.borderTop = "1px solid #eee";
  249. stateManagementDiv.style.paddingTop = "10px";
  250.  
  251. let saveStateButton = document.createElement("button");
  252. saveStateButton.innerText = "Guardar Estado Actual";
  253. saveStateButton.style.padding = "5px 10px";
  254. saveStateButton.style.marginRight = "10px";
  255. saveStateButton.style.background = "#28a745";
  256. saveStateButton.style.color = "white";
  257. saveStateButton.style.border = "none";
  258. saveStateButton.style.borderRadius = "5px";
  259. saveStateButton.style.cursor = "pointer";
  260. saveStateButton.addEventListener("click", (e) => {
  261. e.preventDefault();
  262. saveCurrentState(component, componentId, formGroups, editableProps, signals);
  263. });
  264. stateManagementDiv.appendChild(saveStateButton);
  265.  
  266. let restoreStateButton = document.createElement("button");
  267. restoreStateButton.innerText = "Restaurar Estado";
  268. restoreStateButton.style.padding = "5px 10px";
  269. restoreStateButton.style.background = "#007bff";
  270. restoreStateButton.style.color = "white";
  271. restoreStateButton.style.border = "none";
  272. restoreStateButton.style.borderRadius = "5px";
  273. restoreStateButton.style.cursor = "pointer";
  274.  
  275. if (!savedStates[componentId]) {
  276. restoreStateButton.disabled = true;
  277. restoreStateButton.style.opacity = "0.5";
  278. restoreStateButton.style.cursor = "not-allowed";
  279. }
  280.  
  281. restoreStateButton.addEventListener("click", (e) => {
  282. e.preventDefault();
  283. restoreSavedState(component, componentId, formGroups, editableProps, signals);
  284. });
  285. stateManagementDiv.appendChild(restoreStateButton);
  286.  
  287. modal.appendChild(stateManagementDiv);
  288.  
  289. let fileLabel = document.createElement("label");
  290. fileLabel.innerText = "Cargar JSON:";
  291. fileLabel.style.display = "block";
  292. fileLabel.style.marginTop = "15px";
  293. modal.appendChild(fileLabel);
  294.  
  295. let fileInput = document.createElement("input");
  296. fileInput.type = "file";
  297. fileInput.accept = "application/json";
  298. fileInput.style.marginTop = "5px";
  299. fileInput.style.width = "100%";
  300. fileInput.addEventListener("change", (event) =>
  301. handleFileUpload(event, formGroups, signals)
  302. );
  303. modal.appendChild(fileInput);
  304.  
  305. let exportButton = document.createElement("button");
  306. exportButton.innerText = "Exportar a JSON";
  307. exportButton.style.marginTop = "10px";
  308. exportButton.style.width = "100%";
  309. exportButton.style.padding = "5px";
  310. exportButton.style.background = "#17a2b8";
  311. exportButton.style.color = "white";
  312. exportButton.style.border = "none";
  313. exportButton.style.borderRadius = "5px";
  314. exportButton.style.cursor = "pointer";
  315. exportButton.addEventListener("click", (e) => {
  316. e.preventDefault();
  317. exportToJson(component, formGroups, signals);
  318. });
  319. modal.appendChild(exportButton);
  320.  
  321. let closeButton = document.createElement("button");
  322. closeButton.innerText = "Cerrar";
  323. closeButton.style.marginTop = "10px";
  324. closeButton.style.width = "100%";
  325. closeButton.style.padding = "5px";
  326. closeButton.style.background = "#d9534f";
  327. closeButton.style.color = "white";
  328. closeButton.style.border = "none";
  329. closeButton.style.borderRadius = "5px";
  330. closeButton.style.cursor = "pointer";
  331.  
  332. closeButton.addEventListener("click", () => {
  333. document.body.removeChild(modal);
  334. });
  335.  
  336. modal.appendChild(closeButton);
  337. document.body.appendChild(modal);
  338. }
  339.  
  340. // Function to check if a property is an Angular signal
  341. function isSignal(value) {
  342. return typeof value === 'function' &&
  343. (value.name === 'Signal' ||
  344. (typeof value() !== 'undefined' &&
  345. typeof value.set === 'function'));
  346. }
  347.  
  348. // Function to append a signal field to the form
  349. function appendSignalField(container, component, prop, signal) {
  350. try {
  351. // Get current value
  352. const currentValue = signal();
  353.  
  354. let signalLabel = document.createElement("label");
  355. signalLabel.innerText = `${prop} (signal)`;
  356. signalLabel.style.display = "block";
  357. signalLabel.style.marginTop = "5px";
  358. signalLabel.style.fontWeight = "bold";
  359. signalLabel.style.color = "#007bff";
  360.  
  361. let signalInput = document.createElement("input");
  362. signalInput.style.width = "100%";
  363. signalInput.style.marginTop = "2px";
  364. signalInput.dataset.signalName = prop;
  365.  
  366. if (typeof currentValue === "boolean") {
  367. signalInput.type = "checkbox";
  368. signalInput.checked = currentValue;
  369. } else if (typeof currentValue === "number") {
  370. signalInput.type = "number";
  371. signalInput.value = currentValue;
  372. } else if (typeof currentValue === "string") {
  373. signalInput.type = "text";
  374. signalInput.value = currentValue;
  375. } else if (currentValue === null || currentValue === undefined) {
  376. signalInput.type = "text";
  377. signalInput.value = "";
  378. signalInput.placeholder = "undefined/null";
  379. } else {
  380. // Complex object - not directly editable
  381. let valueInfo = document.createElement("div");
  382. valueInfo.innerText = `Valor complejo (${typeof currentValue}): ${JSON.stringify(currentValue).substring(0, 50)}${JSON.stringify(currentValue).length > 50 ? '...' : ''}`;
  383. valueInfo.style.fontSize = "12px";
  384. valueInfo.style.marginBottom = "10px";
  385. valueInfo.style.color = "#666";
  386. container.appendChild(signalLabel);
  387. container.appendChild(valueInfo);
  388. return;
  389. }
  390.  
  391. signalInput.addEventListener("change", () => {
  392. try {
  393. let newValue;
  394. if (signalInput.type === "checkbox") {
  395. newValue = signalInput.checked;
  396. } else if (signalInput.type === "number") {
  397. newValue = parseFloat(signalInput.value);
  398. } else {
  399. newValue = signalInput.value;
  400. }
  401.  
  402. // Update the signal value using set() method
  403. signal.set(newValue);
  404. console.log(`[Angular Modifier] Se actualizó signal '${prop}' a ${newValue}`);
  405. } catch (err) {
  406. alert(`⚠️ Error al actualizar signal '${prop}': ${err.message}`);
  407. }
  408. });
  409.  
  410. container.appendChild(signalLabel);
  411. container.appendChild(signalInput);
  412.  
  413. return signalInput;
  414. } catch (err) {
  415. console.warn(`[Angular Modifier] Error al procesar signal '${prop}':`, err);
  416. return null;
  417. }
  418. }
  419.  
  420. function appendEditableField(form, component, prop, value) {
  421. let label = document.createElement("label");
  422. label.innerText = prop;
  423. label.style.display = "block";
  424. label.style.marginTop = "5px";
  425.  
  426. let input = document.createElement("input");
  427. input.style.width = "100%";
  428. input.style.marginTop = "2px";
  429. input.dataset.propName = prop;
  430.  
  431. if (typeof value === "boolean") {
  432. input.type = "checkbox";
  433. input.checked = value;
  434. } else if (typeof value === "number") {
  435. input.type = "number";
  436. input.value = value;
  437. } else if (typeof value === "string") {
  438. input.type = "text";
  439. input.value = value;
  440. } else {
  441. return null;
  442. }
  443.  
  444. input.addEventListener("change", () => {
  445. try {
  446. if (input.type === "checkbox") {
  447. component[prop] = input.checked;
  448. } else if (input.type === "number") {
  449. component[prop] = parseFloat(input.value);
  450. } else {
  451. component[prop] = input.value;
  452. }
  453. if (typeof ng.applyChanges === "function") {
  454. ng.applyChanges(component);
  455. console.log(`[Angular Modifier] Se aplicaron cambios en ${prop}`);
  456. }
  457. } catch (err) {
  458. alert(`⚠️ Error al actualizar '${prop}': ${err.message}`);
  459. }
  460. });
  461.  
  462. form.appendChild(label);
  463. form.appendChild(input);
  464. return input;
  465. }
  466.  
  467. function appendFormGroupFields(form, formGroup, formGroupName) {
  468. let formGroupTitle = document.createElement("h4");
  469. formGroupTitle.innerText = `Formulario: ${formGroupName}`;
  470. formGroupTitle.style.marginTop = "10px";
  471. formGroupTitle.style.color = "#007bff";
  472. form.appendChild(formGroupTitle);
  473.  
  474. if (formGroup.controls) {
  475. Object.keys(formGroup.controls).forEach((controlName) => {
  476. try {
  477. const control = formGroup.controls[controlName];
  478. const currentValue = control.value;
  479.  
  480. let controlLabel = document.createElement("label");
  481. controlLabel.innerText = controlName;
  482. controlLabel.style.display = "block";
  483. controlLabel.style.marginTop = "5px";
  484. controlLabel.style.marginLeft = "10px";
  485.  
  486. let controlInput = document.createElement("input");
  487. controlInput.style.width = "95%";
  488. controlInput.style.marginTop = "2px";
  489. controlInput.style.marginLeft = "10px";
  490. controlInput.dataset.formGroup = formGroupName;
  491. controlInput.dataset.controlName = controlName;
  492.  
  493. if (typeof currentValue === "boolean") {
  494. controlInput.type = "checkbox";
  495. controlInput.checked = currentValue;
  496. } else if (typeof currentValue === "number") {
  497. controlInput.type = "number";
  498. controlInput.value = currentValue;
  499. } else {
  500. controlInput.type = "text";
  501. controlInput.value =
  502. currentValue !== null && currentValue !== undefined
  503. ? currentValue
  504. : "";
  505. }
  506.  
  507. controlInput.addEventListener("change", () => {
  508. try {
  509. let newValue;
  510. if (controlInput.type === "checkbox") {
  511. newValue = controlInput.checked;
  512. } else if (controlInput.type === "number") {
  513. newValue = parseFloat(controlInput.value);
  514. } else {
  515. newValue = controlInput.value;
  516. }
  517.  
  518. control.setValue(newValue);
  519. console.log(
  520. `[Angular Modifier] Actualizado control '${controlName}' en FormGroup '${formGroupName}'`
  521. );
  522. } catch (err) {
  523. alert(
  524. `⚠️ Error al actualizar control '${controlName}': ${err.message}`
  525. );
  526. }
  527. });
  528.  
  529. form.appendChild(controlLabel);
  530. form.appendChild(controlInput);
  531. } catch (err) {
  532. console.warn(
  533. `[Angular Modifier] Error al mostrar control '${controlName}':`,
  534. err
  535. );
  536. }
  537. });
  538. }
  539. }
  540.  
  541. function handleFileUpload(event, formGroups, signals) {
  542. let file = event.target.files[0];
  543. if (!file) return;
  544.  
  545. let reader = new FileReader();
  546. reader.onload = function (event) {
  547. try {
  548. let jsonData = JSON.parse(event.target.result);
  549. applyJsonToForm(jsonData, formGroups, signals);
  550. } catch (err) {
  551. alert("⚠️ Error al cargar JSON: " + err.message);
  552. }
  553. };
  554. reader.readAsText(file);
  555. }
  556.  
  557. function applyJsonToForm(jsonData, formGroups, signals) {
  558. if (jsonData.properties) {
  559. Object.keys(jsonData.properties).forEach((prop) => {
  560. if (IGNORED_PROPERTIES.includes(prop)) return;
  561.  
  562. try {
  563. const inputElement = document.querySelector(
  564. `input[data-prop-name="${prop}"]`
  565. );
  566. if (inputElement) {
  567. if (inputElement.type === "checkbox") {
  568. inputElement.checked = jsonData.properties[prop];
  569. } else {
  570. inputElement.value = jsonData.properties[prop];
  571. }
  572. inputElement.dispatchEvent(new Event("change"));
  573. }
  574. } catch (err) {
  575. console.warn(
  576. `[Angular Modifier] Error al aplicar propiedad '${prop}':`,
  577. err
  578. );
  579. }
  580. });
  581. }
  582.  
  583. if (jsonData.signals) {
  584. Object.keys(jsonData.signals).forEach((signalName) => {
  585. if (signals[signalName]) {
  586. try {
  587. const signalValue = jsonData.signals[signalName];
  588. signals[signalName].set(signalValue);
  589.  
  590. const signalInput = document.querySelector(
  591. `input[data-signal-name="${signalName}"]`
  592. );
  593. if (signalInput) {
  594. if (signalInput.type === "checkbox") {
  595. signalInput.checked = signalValue;
  596. } else {
  597. signalInput.value = signalValue;
  598. }
  599. }
  600.  
  601. console.log(`[Angular Modifier] Signal '${signalName}' actualizado`);
  602. } catch (err) {
  603. console.warn(
  604. `[Angular Modifier] Error al actualizar signal '${signalName}':`,
  605. err
  606. );
  607. }
  608. }
  609. });
  610. }
  611.  
  612. if (jsonData.formGroups) {
  613. Object.keys(jsonData.formGroups).forEach((groupName) => {
  614. if (formGroups[groupName]) {
  615. let formGroup = formGroups[groupName];
  616. const groupData = jsonData.formGroups[groupName];
  617.  
  618. Object.keys(groupData).forEach((controlName) => {
  619. if (formGroup.controls[controlName]) {
  620. try {
  621. formGroup.controls[controlName].setValue(
  622. groupData[controlName]
  623. );
  624.  
  625. const controlInput = document.querySelector(
  626. `input[data-form-group="${groupName}"][data-control-name="${controlName}"]`
  627. );
  628. if (controlInput) {
  629. if (controlInput.type === "checkbox") {
  630. controlInput.checked = groupData[controlName];
  631. } else {
  632. controlInput.value = groupData[controlName];
  633. }
  634. }
  635.  
  636. console.log(
  637. `[Angular Modifier] Campo '${controlName}' de '${groupName}' actualizado`
  638. );
  639. } catch (err) {
  640. console.warn(
  641. `[Angular Modifier] Error al actualizar control '${controlName}':`,
  642. err
  643. );
  644. }
  645. }
  646. });
  647. }
  648. });
  649. }
  650. }
  651.  
  652. function exportToJson(component, formGroups, signals) {
  653. let exportData = {
  654. properties: {},
  655. formGroups: {},
  656. signals: {}
  657. };
  658.  
  659. Object.keys(component).forEach((prop) => {
  660. if (IGNORED_PROPERTIES.includes(prop)) return;
  661.  
  662. let value = component[prop];
  663. if (
  664. typeof value !== "function" &&
  665. value !== null &&
  666. typeof value !== "object"
  667. ) {
  668. exportData.properties[prop] = value;
  669. }
  670. });
  671.  
  672. // Export signals
  673. Object.keys(signals).forEach((signalName) => {
  674. try {
  675. const signal = signals[signalName];
  676. const value = signal();
  677.  
  678. // Only export primitive values that can be safely serialized
  679. if (value === null || typeof value === "undefined" ||
  680. typeof value === "string" ||
  681. typeof value === "number" ||
  682. typeof value === "boolean") {
  683. exportData.signals[signalName] = value;
  684. }
  685. } catch (err) {
  686. console.warn(
  687. `[Angular Modifier] Error al exportar signal '${signalName}':`,
  688. err
  689. );
  690. }
  691. });
  692.  
  693. Object.keys(formGroups).forEach((groupName) => {
  694. const formGroup = formGroups[groupName];
  695. exportData.formGroups[groupName] = {};
  696.  
  697. if (formGroup.controls) {
  698. Object.keys(formGroup.controls).forEach((controlName) => {
  699. try {
  700. exportData.formGroups[groupName][controlName] =
  701. formGroup.controls[controlName].value;
  702. } catch (err) {
  703. console.warn(
  704. `[Angular Modifier] Error al exportar control '${controlName}':`,
  705. err
  706. );
  707. }
  708. });
  709. }
  710. });
  711.  
  712. const jsonString = JSON.stringify(exportData, null, 2);
  713. const blob = new Blob([jsonString], { type: "application/json" });
  714. const url = URL.createObjectURL(blob);
  715.  
  716. const a = document.createElement("a");
  717. a.href = url;
  718. a.download = `angular-component-${Date.now()}.json`;
  719. document.body.appendChild(a);
  720. a.click();
  721. document.body.removeChild(a);
  722. URL.revokeObjectURL(url);
  723. }
  724.  
  725. function saveCurrentState(component, componentId, formGroups, editableProps, signals) {
  726. let state = {
  727. properties: {},
  728. formGroups: {},
  729. signals: {}
  730. };
  731.  
  732. Object.keys(editableProps).forEach((prop) => {
  733. if (IGNORED_PROPERTIES.includes(prop)) return;
  734.  
  735. const input = editableProps[prop].input;
  736. if (input.type === "checkbox") {
  737. state.properties[prop] = input.checked;
  738. } else if (input.type === "number") {
  739. state.properties[prop] = parseFloat(input.value);
  740. } else {
  741. state.properties[prop] = input.value;
  742. }
  743. });
  744.  
  745. // Save signal values
  746. Object.keys(signals).forEach((signalName) => {
  747. try {
  748. const signal = signals[signalName];
  749. const value = signal();
  750.  
  751. // Only save primitive values that can be safely serialized
  752. if (value === null || typeof value === "undefined" ||
  753. typeof value === "string" ||
  754. typeof value === "number" ||
  755. typeof value === "boolean") {
  756. state.signals[signalName] = value;
  757. }
  758. } catch (err) {
  759. console.warn(
  760. `[Angular Modifier] Error al guardar signal '${signalName}':`,
  761. err
  762. );
  763. }
  764. });
  765.  
  766. Object.keys(formGroups).forEach((groupName) => {
  767. const formGroup = formGroups[groupName];
  768. state.formGroups[groupName] = {};
  769.  
  770. if (formGroup.controls) {
  771. Object.keys(formGroup.controls).forEach((controlName) => {
  772. try {
  773. state.formGroups[groupName][controlName] =
  774. formGroup.controls[controlName].value;
  775. } catch (err) {
  776. console.warn(
  777. `[Angular Modifier] Error al guardar control '${controlName}':`,
  778. err
  779. );
  780. }
  781. });
  782. }
  783. });
  784.  
  785. savedStates[componentId] = state;
  786.  
  787. try {
  788. localStorage.setItem(STORAGE_KEY, JSON.stringify(savedStates));
  789. alert(`✅ Estado guardado correctamente para ${componentId}`);
  790. } catch (err) {
  791. console.error("[Angular Modifier] Error al guardar estado:", err);
  792. alert("⚠️ Error al guardar estado: " + err.message);
  793. }
  794. }
  795.  
  796. function restoreSavedState(
  797. component,
  798. componentId,
  799. formGroups,
  800. editableProps,
  801. signals
  802. ) {
  803. const savedState = savedStates[componentId];
  804. if (!savedState) {
  805. alert("⚠️ No hay estado guardado para este componente");
  806. return;
  807. }
  808.  
  809. if (savedState.properties) {
  810. Object.keys(savedState.properties).forEach((prop) => {
  811. if (IGNORED_PROPERTIES.includes(prop)) return;
  812.  
  813. if (editableProps[prop]) {
  814. const input = editableProps[prop].input;
  815. const value = savedState.properties[prop];
  816.  
  817. if (input.type === "checkbox") {
  818. input.checked = value;
  819. } else {
  820. input.value = value;
  821. }
  822.  
  823. try {
  824. component[prop] = value;
  825. } catch (err) {
  826. console.warn(
  827. `[Angular Modifier] Error al restaurar propiedad '${prop}':`,
  828. err
  829. );
  830. }
  831. }
  832. });
  833. }
  834.  
  835. // Restore signal values
  836. if (savedState.signals) {
  837. Object.keys(savedState.signals).forEach((signalName) => {
  838. if (signals[signalName]) {
  839. try {
  840. const signalValue = savedState.signals[signalName];
  841. signals[signalName].set(signalValue);
  842.  
  843. const signalInput = document.querySelector(
  844. `input[data-signal-name="${signalName}"]`
  845. );
  846. if (signalInput) {
  847. if (signalInput.type === "checkbox") {
  848. signalInput.checked = signalValue;
  849. } else {
  850. signalInput.value = signalValue;
  851. }
  852. }
  853.  
  854. console.log(`[Angular Modifier] Signal '${signalName}' restaurado`);
  855. } catch (err) {
  856. console.warn(
  857. `[Angular Modifier] Error al restaurar signal '${signalName}':`,
  858. err
  859. );
  860. }
  861. }
  862. });
  863. }
  864.  
  865. if (savedState.formGroups) {
  866. Object.keys(savedState.formGroups).forEach((groupName) => {
  867. if (formGroups[groupName]) {
  868. const formGroup = formGroups[groupName];
  869. const groupData = savedState.formGroups[groupName];
  870.  
  871. Object.keys(groupData).forEach((controlName) => {
  872. if (formGroup.controls[controlName]) {
  873. try {
  874. formGroup.controls[controlName].setValue(
  875. groupData[controlName]
  876. );
  877.  
  878. const controlInput = document.querySelector(
  879. `input[data-form-group="${groupName}"][data-control-name="${controlName}"]`
  880. );
  881. if (controlInput) {
  882. if (controlInput.type === "checkbox") {
  883. controlInput.checked = groupData[controlName];
  884. } else {
  885. controlInput.value = groupData[controlName];
  886. }
  887. }
  888. } catch (err) {
  889. console.warn(
  890. `[Angular Modifier] Error al restaurar control '${controlName}':`,
  891. err
  892. );
  893. }
  894. }
  895. });
  896. }
  897. });
  898. }
  899.  
  900. if (typeof ng.applyChanges === "function") {
  901. ng.applyChanges(component);
  902. console.log(`[Angular Modifier] Se restauró el estado del componente`);
  903. }
  904.  
  905. alert(`✅ Estado restaurado correctamente para ${componentId}`);
  906. }
  907. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址