ide-assi 0.444.0 → 0.445.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10977,6 +10977,7 @@ class ninegrid {
10977
10977
  ROW_MOVED : "rowmoved",
10978
10978
  //CAN_FIXED_CHANGE : "canfixedchange",
10979
10979
  //FIXED_CHANGED : "fixedchanged",
10980
+ TAB_LOAD : "tabload",
10980
10981
  };
10981
10982
 
10982
10983
  static BAND = {
@@ -119286,7 +119287,21 @@ class NineGridContainer extends HTMLElement
119286
119287
 
119287
119288
  //export { NineGridContainer };
119288
119289
 
119289
- customElements.define("nine-grid", NineGridContainer);
119290
+ customElements.define("nine-grid", NineGridContainer);
119291
+
119292
+ class NineGridSourceContainer extends NineGridContainer
119293
+ {
119294
+ constructor() {
119295
+
119296
+ super();
119297
+ }
119298
+
119299
+ connectedCallback() {
119300
+ super.connectedCallback();
119301
+ };
119302
+ }
119303
+
119304
+ customElements.define("ng-source", NineGridSourceContainer);
119290
119305
 
119291
119306
  class nxDialog extends HTMLElement
119292
119307
  {
@@ -119426,76 +119441,6 @@ class nxDialog extends HTMLElement
119426
119441
 
119427
119442
  customElements.define("nx-dialog", nxDialog);
119428
119443
 
119429
- class nxDiv extends HTMLElement {
119430
- constructor() {
119431
- super();
119432
- this.attachShadow({ mode: "open" });
119433
- this.isCollapsed = false; // 검색 박스 상태 (true: 축소됨, false: 확장됨)
119434
- }
119435
-
119436
- connectedCallback() {
119437
- this.#init();
119438
- }
119439
-
119440
- getJsonData = () => {
119441
- const parent = this.shadowRoot.querySelector(".search-content");
119442
- const elements = parent.querySelectorAll("[id]");
119443
- const jsonData = {};
119444
-
119445
- elements.forEach(element => {
119446
- if (element.tagName === "INPUT" || element.tagName === "TEXTAREA" || element.tagName === "SELECT") {
119447
- jsonData[element.id] = element.value;
119448
- } else {
119449
- jsonData[element.id] = element.textContent.trim();
119450
- }
119451
- });
119452
-
119453
- return jsonData;
119454
- };
119455
-
119456
- getSearchOptions = () => {
119457
-
119458
- };
119459
-
119460
- #init = () => {
119461
- const contents = this.innerHTML.trim();
119462
- this.innerHTML = ""; // 기존 내부 HTML 제거
119463
-
119464
- const htmlTmpl = document.createElement("template");
119465
- htmlTmpl.innerHTML = `
119466
- <style>
119467
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/nxDiv.css";
119468
- ${ninegrid.getCustomPath(this,"nxDiv.css")}
119469
-
119470
- :host {
119471
- }
119472
- </style>
119473
-
119474
- <div class="search-container">
119475
- <span class="toggle-icon"></span>
119476
- <div class="search-content">${contents}</div>
119477
- </div>
119478
- `;
119479
-
119480
- this.shadowRoot.appendChild(htmlTmpl.content.cloneNode(true));
119481
-
119482
- this.#setupToggle();
119483
- }
119484
-
119485
- #setupToggle = () => {
119486
- const toggleIcon = this.shadowRoot.querySelector(".toggle-icon");
119487
- const searchContainer = this.shadowRoot.querySelector(".search-container");
119488
-
119489
- toggleIcon.addEventListener("click", () => {
119490
- this.isCollapsed = !this.isCollapsed;
119491
- //toggleIcon.innerHTML = this.isCollapsed ? "" : "X";
119492
- searchContainer.classList.toggle("collapse", this.isCollapsed);
119493
- });
119494
- }
119495
- }
119496
-
119497
- customElements.define("nx-div", nxDiv);
119498
-
119499
119444
  class nxI18nExt extends HTMLElement
119500
119445
  {
119501
119446
  #enableHtml;
@@ -120123,6 +120068,20 @@ class nxSideMenu extends HTMLElement
120123
120068
  ninegrid.waitForInnerHTML(this)
120124
120069
  .then(() => this.#init())
120125
120070
  .catch(error => console.error(error));
120071
+
120072
+ setTimeout(() => {
120073
+ if (!ninegrid.querySelector("nx-side-menu-item.active", this.shadowRoot)) {
120074
+ const rawUrl = window.location.href; // 전체 URL
120075
+ const parsedUrl = new URL(rawUrl); // URL 객체로 파싱
120076
+ const href = parsedUrl.pathname; // 👉 경로만 추출
120077
+
120078
+ const el = ninegrid.querySelector(`nx-side-menu-item[href="${href}"]`, this.shadowRoot);
120079
+ if (el) {
120080
+ el.classList.add("active");
120081
+ }
120082
+ }
120083
+ }, 300);
120084
+
120126
120085
  };
120127
120086
 
120128
120087
  expand = () => {
@@ -120654,203 +120613,6 @@ class nxSpan extends HTMLElement
120654
120613
 
120655
120614
  customElements.define("nx-span", nxSpan);
120656
120615
 
120657
- class nxTab extends HTMLElement {
120658
- constructor() {
120659
- super();
120660
- this.attachShadow({ mode: 'open' });
120661
-
120662
- this.shadowRoot.innerHTML = `
120663
- <style>
120664
- :host(.theme-1) {
120665
- .tabs {
120666
- --border-bottom: 1px solid #eee;
120667
- gap: 3px;
120668
- }
120669
- .tab-button {
120670
- color: #666;
120671
- font-weight: 500;
120672
- transform: translateY(1px);
120673
- z-index: 1;
120674
- --border-bottom: 1px solid #ccc;
120675
- }
120676
- .tab-button.active {
120677
- color: #666;
120678
- font-weight: 700;
120679
- border-bottom: 1px solid white;
120680
- }
120681
- .tab-pages {
120682
- border: 1px solid #ccc;
120683
- }
120684
- }
120685
-
120686
- :host(.theme-2) {
120687
- .tabs {
120688
- border-bottom: 1px solid #eee;
120689
- gap: 40px;
120690
- }
120691
- .tab-button {
120692
- color: #666;
120693
- font-weight: 500;
120694
- border: none;
120695
- }
120696
- .tab-button.active {
120697
- color: green;
120698
- font-weight: 700;
120699
- border-bottom: 3px solid green;
120700
- }
120701
- }
120702
-
120703
- .tabs {
120704
- display: flex;
120705
- cursor: pointer;
120706
-
120707
- }
120708
- .tab-button {
120709
- padding: 8px;
120710
- border: 1px solid #ccc;
120711
- text-align: center;
120712
- outline: 0;
120713
- }
120714
- .tab-button:hover {
120715
- --filter: brightness(80%);
120716
- }
120717
- .tab-pages {
120718
- position: relative;
120719
- width: 100%;
120720
- overflow: hidden;
120721
- transition: height 0.5s ease-in-out;
120722
- }
120723
- .tab-page {
120724
- position: absolute;
120725
- width: 100%;
120726
- top: 0;
120727
- transition: left 0.5s ease-in-out;
120728
- padding: 20px;
120729
- box-sizing: border-box;
120730
- white-space: nowrap;
120731
- overflow: hidden;
120732
- text-overflow: ellipsis;
120733
- text-align: left;
120734
- }
120735
- .tab-page.active {
120736
- left: 0;
120737
- }
120738
- .tab-page.exit-left {
120739
- left: -100%;
120740
- }
120741
- .tab-page.exit-right {
120742
- left: 100%;
120743
- }
120744
- </style>
120745
- <div class="tabs"></div>
120746
- <div class="tab-pages"></div>
120747
- `;
120748
-
120749
- //console.log(this.shadowRoot.querySelector('.tab-page'));
120750
- //this.shadowRoot.querySelector('.tab-pages').style.height = this.shadowRoot.querySelector('.tab-page').style.height;
120751
-
120752
- this.switchTabHandler = this.#switchTab.bind(this);
120753
- }
120754
-
120755
- connectedCallback() {
120756
- this.classList.add(this.getAttribute("theme") || "theme-1");
120757
- this.#renderTabs();
120758
- this.shadowRoot.querySelectorAll('.tab-button').forEach(tab => {
120759
- tab.addEventListener('click', this.switchTabHandler);
120760
- });
120761
-
120762
- const firstTab = this.shadowRoot.querySelector('.tab-button');
120763
- const firstContent = this.shadowRoot.querySelector('.tab-page');
120764
- if (firstTab && firstContent) {
120765
- firstTab.classList.add('active');
120766
- firstContent.classList.add('active');
120767
- setTimeout(() => {
120768
- this.shadowRoot.querySelector('.tab-pages').style.height = `${firstContent.scrollHeight}px`;
120769
- }, 100);
120770
- //this.shadowRoot.querySelector('.tab-pages').style.height = `${firstContent.scrollHeight}px`;
120771
-
120772
- console.log(firstContent.style.height, firstContent.scrollHeight);
120773
- }
120774
-
120775
- this.shadowRoot.querySelectorAll('.tab-page:not(.active)').forEach(el => { el.classList.add('exit-right'); });
120776
- }
120777
-
120778
- #renderTabs() {
120779
- const tabs = this.shadowRoot.querySelector('.tabs');
120780
- const contents = this.shadowRoot.querySelector('.tab-pages');
120781
- const tabItems = this.querySelectorAll('nx-tab-page');
120782
-
120783
- tabItems.forEach((item, index) => {
120784
- const tab = document.createElement('div');
120785
- tab.classList.add('tab-button');
120786
- tab.textContent = item.getAttribute('caption');
120787
- tab.setAttribute('data-target', `content${index}`);
120788
- tabs.appendChild(tab);
120789
-
120790
- const content = document.createElement('div');
120791
- content.id = `content${index}`;
120792
- content.classList.add('tab-page');
120793
- content.innerHTML = item.innerHTML;
120794
- contents.appendChild(content);
120795
- });
120796
-
120797
- tabItems.forEach((item) => {
120798
- item.remove();
120799
- });
120800
- }
120801
-
120802
- #switchTab(event) {
120803
- const target = event.target;
120804
- if (!target.classList.contains('tab-button')) return;
120805
-
120806
- const targetId = target.getAttribute('data-target');
120807
- const activeTab = this.shadowRoot.querySelector('.tab-button.active');
120808
- const activeContent = this.shadowRoot.querySelector('.tab-page.active');
120809
- const newContent = this.shadowRoot.getElementById(targetId);
120810
-
120811
- if (activeTab === target) return; // 현재 탭을 클릭했을 때 아무런 변화가 없도록 함
120812
-
120813
- if (activeTab && activeContent) {
120814
- activeTab.classList.remove('active');
120815
- activeContent.classList.remove('active','exit-left','exit-right');
120816
- //newContent.classList.remove('exit-left','exit-right');
120817
- activeContent.style.left = '';
120818
-
120819
- if (activeTab.compareDocumentPosition(target) & Node.DOCUMENT_POSITION_FOLLOWING) {
120820
- activeContent.classList.add('exit-left');
120821
- } else {
120822
- activeContent.classList.add('exit-right');
120823
- }
120824
- //console.log(index);
120825
- //if (index == 1) return;
120826
-
120827
- newContent.classList.add('active');
120828
- newContent.style.left = '';
120829
-
120830
- newContent.classList.remove('exit-left','exit-right');
120831
- }
120832
-
120833
- target.classList.add('active');
120834
- //newContent.classList.add('active');
120835
- this.shadowRoot.querySelector('.tab-pages').style.height = `${newContent.scrollHeight}px`;
120836
-
120837
- //console.log(newContent.style.height, newContent.scrollHeight);
120838
- }
120839
- }
120840
-
120841
- class nxTabPage extends HTMLElement {
120842
- constructor() {
120843
- super();
120844
- }
120845
-
120846
- connectedCallback() {
120847
- this.caption = this.getAttribute('caption');
120848
- }
120849
- }
120850
-
120851
- customElements.define('nx-tab', nxTab);
120852
- customElements.define('nx-tab-page', nxTabPage);
120853
-
120854
120616
  class nxTest1 extends HTMLElement
120855
120617
  {
120856
120618
  constructor() {
@@ -121035,6 +120797,704 @@ Array.prototype.nineBinarySearch = function(target)
121035
120797
  return -1;
121036
120798
  };
121037
120799
 
120800
+ class nxDiv extends HTMLElement {
120801
+ constructor() {
120802
+ super();
120803
+ this.attachShadow({ mode: "open" });
120804
+ this.isCollapsed = false; // 검색 박스 상태 (true: 축소됨, false: 확장됨)
120805
+ }
120806
+
120807
+ connectedCallback() {
120808
+ this.#init();
120809
+ }
120810
+
120811
+ getJsonData = () => {
120812
+ const parent = this.shadowRoot.querySelector(".search-content");
120813
+ const elements = parent.querySelectorAll("[id]");
120814
+ const jsonData = {};
120815
+
120816
+ elements.forEach(element => {
120817
+ if (element.tagName === "INPUT" || element.tagName === "TEXTAREA" || element.tagName === "SELECT") {
120818
+ jsonData[element.id] = element.value;
120819
+ } else {
120820
+ jsonData[element.id] = element.textContent.trim();
120821
+ }
120822
+ });
120823
+
120824
+ return jsonData;
120825
+ };
120826
+
120827
+ getSearchOptions = () => {
120828
+
120829
+ };
120830
+
120831
+ #init = () => {
120832
+ const contents = this.innerHTML.trim();
120833
+ this.innerHTML = ""; // 기존 내부 HTML 제거
120834
+
120835
+ const htmlTmpl = document.createElement("template");
120836
+ htmlTmpl.innerHTML = `
120837
+ <style>
120838
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/nxDiv.css";
120839
+ ${ninegrid.getCustomPath(this,"nxDiv.css")}
120840
+ </style>
120841
+
120842
+ <div class="search-container">
120843
+ <span class="toggle-icon"></span>
120844
+ <div class="search-content">${contents}</div>
120845
+ </div>
120846
+ `;
120847
+
120848
+ this.shadowRoot.appendChild(htmlTmpl.content.cloneNode(true));
120849
+
120850
+ this.#setupToggle();
120851
+ }
120852
+
120853
+ #setupToggle = () => {
120854
+ const toggleIcon = this.shadowRoot.querySelector(".toggle-icon");
120855
+ const searchContainer = this.shadowRoot.querySelector(".search-container");
120856
+
120857
+ toggleIcon.addEventListener("click", () => {
120858
+ this.isCollapsed = !this.isCollapsed;
120859
+ //toggleIcon.innerHTML = this.isCollapsed ? "" : "X";
120860
+ searchContainer.classList.toggle("collapse", this.isCollapsed);
120861
+ });
120862
+ }
120863
+ }
120864
+
120865
+ customElements.define("nx-div", nxDiv);
120866
+
120867
+ class nxCollapse extends HTMLElement {
120868
+ #target;
120869
+ #targetPrevDisplay;
120870
+
120871
+ constructor() {
120872
+ super();
120873
+ this.attachShadow({ mode: "open" });
120874
+ }
120875
+
120876
+ connectedCallback() {
120877
+ const targetSelector = this.getAttribute("target");
120878
+ this.#target = ninegrid.querySelector(targetSelector);
120879
+ //if (!this.#target) return;
120880
+
120881
+ this.#init();
120882
+ }
120883
+
120884
+ #init = () => {
120885
+ this.innerHTML.trim();
120886
+ this.innerHTML = ""; // 기존 내부 HTML 제거
120887
+
120888
+ const htmlTmpl = document.createElement("template");
120889
+ htmlTmpl.innerHTML = `
120890
+ <style>
120891
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/nxCollapse.css";
120892
+ ${ninegrid.getCustomPath(this,"nxCollapse.css")}
120893
+ </style>
120894
+
120895
+ <button></button>
120896
+ `;
120897
+
120898
+ this.shadowRoot.appendChild(htmlTmpl.content.cloneNode(true));
120899
+
120900
+ if (this.#target && this.#target) {
120901
+ const collapseBtn = document.createElement("span");
120902
+ collapseBtn.className = "collapse-toggle";
120903
+
120904
+ const shadowRoot = this.#target.shadowRoot;
120905
+ (shadowRoot || this.#target).appendChild(collapseBtn);
120906
+
120907
+ const style = document.createElement("style");
120908
+ style.textContent = `
120909
+ span.collapse-toggle {
120910
+ position: absolute;
120911
+ top: 0;
120912
+ right: 0;
120913
+ cursor: pointer;
120914
+ width: 16px;
120915
+ height: 16px;
120916
+ border: none;
120917
+ transition: all 0.3s ease;
120918
+ background-repeat: no-repeat;
120919
+ background-position: center;
120920
+ background-size: auto;
120921
+ background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="darkgray" class="bi bi-box-arrow-in-up-right" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M6.364 13.5a.5.5 0 0 0 .5.5H13.5a1.5 1.5 0 0 0 1.5-1.5v-10A1.5 1.5 0 0 0 13.5 1h-10A1.5 1.5 0 0 0 2 2.5v6.636a.5.5 0 1 0 1 0V2.5a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 .5.5v10a.5.5 0 0 1-.5.5H6.864a.5.5 0 0 0-.5.5"/><path fill-rule="evenodd" d="M11 5.5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793l-8.147 8.146a.5.5 0 0 0 .708.708L10 6.707V10.5a.5.5 0 0 0 1 0z"/></svg>');
120922
+ }
120923
+
120924
+ span.collapse-toggle:hover {
120925
+ background-color: rgba(60, 60, 60, 0.1);
120926
+ box-shadow: 0 0 6px rgba(60, 60, 60, 0.4);
120927
+ transform: scale(1.2); /* 살짝 확대 */
120928
+ }
120929
+
120930
+ ${shadowRoot ? ":host(.nx-collapse)" : ".nx-collapse"} {
120931
+ animation: collapseShrink 0.7s ease forwards;
120932
+ pointer-events: none; /* 클릭 방지 */
120933
+ }
120934
+
120935
+ ${shadowRoot ? ":host(.nx-expand)" : ".nx-expand"} {
120936
+ animation: expandShrink 0.6s ease forwards;
120937
+ pointer-events: none; /* 클릭 방지 */
120938
+ }
120939
+
120940
+ @keyframes collapseShrink {
120941
+ 0% {
120942
+ opacity: 1;
120943
+ transform: scale(1);
120944
+ transform-origin: top right;
120945
+ }
120946
+ 100% {
120947
+ opacity: 0;
120948
+ transform: scale(0);
120949
+ transform-origin: top right;
120950
+ }
120951
+ }
120952
+
120953
+ @keyframes expandShrink {
120954
+ 0% {
120955
+ opacity: 0;
120956
+ transform: scale(0.5);
120957
+ transform-origin: top right;
120958
+ }
120959
+ 100% {
120960
+ opacity: 1;
120961
+ transform: scale(1);
120962
+ transform-origin: top right;
120963
+ }
120964
+ }
120965
+ `;
120966
+
120967
+ (shadowRoot || this.#target).appendChild(style);
120968
+
120969
+ // collapse 시 target 숨기고 복원 버튼 등장
120970
+ collapseBtn.addEventListener("click", () => {
120971
+ // fade-out 클래스 먼저 넣고 → 일정 시간 후 display: none
120972
+ // STEP 1: 복원 버튼 애니메이션 시작 (살짝 빠르게 먼저 등장)
120973
+ this.#target.classList.add("nx-collapse");
120974
+
120975
+ setTimeout(() => {
120976
+ // STEP 2: target이 줄어드는 shrinking 애니메이션 시작
120977
+ //this.style.display = "inline-block";
120978
+ this.classList.add("nx-collapse");
120979
+ }, 100); // appearing 시작 후 100ms 딜레이로 target 축소 시작
120980
+
120981
+ setTimeout(() => {
120982
+ // STEP 3: 실제 target 숨기기
120983
+ this.#targetPrevDisplay = this.#target.style.display;
120984
+ this.#target.style.display = "none";
120985
+ }, 500); // shrinking 애니메이션이 끝나고 실제 제거
120986
+ });
120987
+ }
120988
+
120989
+ // 복원 시 target 보이고 복원 버튼 사라짐
120990
+ /**
120991
+ this.shadowRoot.querySelector("button").addEventListener("click", () => {
120992
+ this.#target.classList.remove("nx-collapse");
120993
+ this.classList.remove("nx-collapse");
120994
+
120995
+ this.#target.style.display = this.#targetPrevDisplay;
120996
+ this.style.display = "none";
120997
+ }); */
120998
+
120999
+ this.shadowRoot.querySelector("button").addEventListener("click", () => {
121000
+ // STEP 1: 복원 대상에 애니메이션 클래스 부여
121001
+ this.#target.style.display = this.#targetPrevDisplay;
121002
+ this.#target.classList.remove("nx-collapse");
121003
+ this.#target.classList.add("nx-expand"); // expandReveal 적용
121004
+
121005
+ setTimeout(() => {
121006
+ // STEP 2: target이 줄어드는 shrinking 애니메이션 시작
121007
+ this.classList.remove("nx-collapse");
121008
+ //this.style.display = "inline-block";
121009
+ this.classList.add("nx-expand");
121010
+ }, 100); // appearing 시작 후 100ms 딜레이로 target 축소 시작
121011
+
121012
+
121013
+ setTimeout(() => {
121014
+ // STEP 2: 실제 화면에 표시
121015
+
121016
+ //this.style.display = "none"; // 복원 버튼은 사라짐
121017
+
121018
+ // STEP 3: 애니메이션이 끝난 후 pointer-events 복구 (선택적)
121019
+ this.#target.classList.remove("nx-expand");
121020
+ this.classList.remove("nx-expand");
121021
+ //this.#target.style.pointerEvents = "auto";
121022
+ }, 600); // 애니메이션과 동일한 시간
121023
+ });
121024
+
121025
+ }
121026
+ }
121027
+
121028
+ customElements.define("nx-collapse", nxCollapse);
121029
+
121030
+ class nxTab extends HTMLElement {
121031
+ constructor() {
121032
+ super();
121033
+ this.attachShadow({ mode: 'open' });
121034
+
121035
+ this.shadowRoot.innerHTML = `
121036
+ <style>
121037
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/nxTab.css";
121038
+ ${ninegrid.getCustomPath(this,"nxTab.css")}
121039
+ </style>
121040
+
121041
+ <div class="tabs"></div>
121042
+ <div class="tab-pages"></div>
121043
+ `;
121044
+
121045
+ //console.log(this.shadowRoot.querySelector('.tab-page'));
121046
+ //this.shadowRoot.querySelector('.tab-pages').style.height = this.shadowRoot.querySelector('.tab-page').style.height;
121047
+
121048
+ this.switchTabHandler = this.#switchTab.bind(this);
121049
+ }
121050
+
121051
+ connectedCallback() {
121052
+ this.#init();
121053
+ this.dispatchEvent(new CustomEvent(ninegrid.EVENT.TAB_LOAD, { bubbles: true, detail: {} }));
121054
+ }
121055
+
121056
+ #renderTabs() {
121057
+ const tabs = this.shadowRoot.querySelector('.tabs');
121058
+ const contents = this.shadowRoot.querySelector('.tab-pages');
121059
+ const tabItems = this.querySelectorAll('nx-tab-page');
121060
+
121061
+ tabItems.forEach((item, index) => {
121062
+ const tab = document.createElement('div');
121063
+ tab.classList.add('tab-button');
121064
+ tab.textContent = item.getAttribute('caption');
121065
+ tab.setAttribute('data-target', `content${index}`);
121066
+ tabs.appendChild(tab);
121067
+
121068
+ const content = document.createElement('div');
121069
+ content.id = `content${index}`;
121070
+ content.classList.add('tab-page');
121071
+ content.innerHTML = item.innerHTML;
121072
+ contents.appendChild(content);
121073
+ });
121074
+
121075
+ tabItems.forEach((item) => {
121076
+ item.remove();
121077
+ });
121078
+ }
121079
+
121080
+ #switchTab(event) {
121081
+ const target = event.target;
121082
+ if (!target.classList.contains('tab-button')) return;
121083
+
121084
+ const targetId = target.getAttribute('data-target');
121085
+ const activeTab = this.shadowRoot.querySelector('.tab-button.active');
121086
+ const activeContent = this.shadowRoot.querySelector('.tab-page.active');
121087
+ const newContent = this.shadowRoot.getElementById(targetId);
121088
+
121089
+ if (activeTab === target) return; // 현재 탭을 클릭했을 때 아무런 변화가 없도록 함
121090
+
121091
+ if (activeTab && activeContent) {
121092
+ activeTab.classList.remove('active');
121093
+ activeContent.classList.remove('active','exit-left','exit-right');
121094
+ //newContent.classList.remove('exit-left','exit-right');
121095
+ activeContent.style.left = '';
121096
+
121097
+ if (activeTab.compareDocumentPosition(target) & Node.DOCUMENT_POSITION_FOLLOWING) {
121098
+ activeContent.classList.add('exit-left');
121099
+ } else {
121100
+ activeContent.classList.add('exit-right');
121101
+ }
121102
+ //console.log(index);
121103
+ //if (index == 1) return;
121104
+
121105
+ newContent.classList.add('active');
121106
+ newContent.style.left = '';
121107
+
121108
+ newContent.classList.remove('exit-left','exit-right');
121109
+ }
121110
+
121111
+ target.classList.add('active');
121112
+ //newContent.classList.add('active');
121113
+ this.shadowRoot.querySelector('.tab-pages').style.height = `${newContent.scrollHeight}px`;
121114
+
121115
+ //console.log(newContent.style.height, newContent.scrollHeight);
121116
+ };
121117
+
121118
+ #init = () => {
121119
+
121120
+ this.classList.add(this.getAttribute("theme") || "theme-1");
121121
+ this.#renderTabs();
121122
+ this.shadowRoot.querySelectorAll('.tab-button').forEach(tab => {
121123
+ tab.addEventListener('click', this.switchTabHandler);
121124
+ });
121125
+
121126
+ const firstTab = this.shadowRoot.querySelector('.tab-button');
121127
+ const firstContent = this.shadowRoot.querySelector('.tab-page');
121128
+ if (firstTab && firstContent) {
121129
+ firstTab.classList.add('active');
121130
+ firstContent.classList.add('active');
121131
+ setTimeout(() => {
121132
+ this.shadowRoot.querySelector('.tab-pages').style.height = `${firstContent.scrollHeight}px`;
121133
+ }, 100);
121134
+ //this.shadowRoot.querySelector('.tab-pages').style.height = `${firstContent.scrollHeight}px`;
121135
+
121136
+ console.log(firstContent.style.height, firstContent.scrollHeight);
121137
+ }
121138
+
121139
+ this.shadowRoot.querySelectorAll('.tab-page:not(.active)').forEach(el => { el.classList.add('exit-right'); });
121140
+
121141
+ const resizeObserver = new ResizeObserver(() => {
121142
+ const activeContent = this.shadowRoot.querySelector('.tab-page.active');
121143
+ if (activeContent) {
121144
+ this.shadowRoot.querySelector('.tab-pages').style.height = `${activeContent.scrollHeight}px`;
121145
+ }
121146
+ });
121147
+
121148
+ const activeContent = this.shadowRoot.querySelector('.tab-page.active');
121149
+ if (activeContent) resizeObserver.observe(activeContent);
121150
+ };
121151
+ }
121152
+
121153
+ class nxTabPage extends HTMLElement {
121154
+ constructor() {
121155
+ super();
121156
+ }
121157
+
121158
+ connectedCallback() {
121159
+ this.caption = this.getAttribute('caption');
121160
+ }
121161
+ }
121162
+
121163
+ customElements.define('nx-tab', nxTab);
121164
+ customElements.define('nx-tab-page', nxTabPage);
121165
+
121166
+ class nxSplitter extends HTMLElement {
121167
+ #mode;
121168
+
121169
+ constructor() {
121170
+ super();
121171
+ this.attachShadow({ mode: "open" });
121172
+ }
121173
+
121174
+ connectedCallback() {
121175
+ this.#init();
121176
+ }
121177
+
121178
+ #detectMode = (el) => {
121179
+ const prev = el.previousElementSibling;
121180
+ const next = el.nextElementSibling;
121181
+
121182
+ const prevRect = prev?.getBoundingClientRect();
121183
+ const nextRect = next?.getBoundingClientRect();
121184
+
121185
+ this.#mode = (!prevRect || !nextRect) || (Math.abs(prevRect.top - nextRect.top) < 5) ? "h" : "v";
121186
+ };
121187
+
121188
+ #startDrag = (e) => {
121189
+ e.preventDefault();
121190
+ e.stopPropagation();
121191
+
121192
+ const splitterRect = this.getBoundingClientRect();
121193
+ const isHorizontal = this.#mode === "h";
121194
+
121195
+ const clickOffset = isHorizontal
121196
+ ? e.clientX - splitterRect.left
121197
+ : e.clientY - splitterRect.top;
121198
+
121199
+ const dragBar = document.createElement("div");
121200
+ dragBar.className = `nx-splitter-drag-bar-${this.#mode}`;
121201
+
121202
+ Object.assign(dragBar.style, {
121203
+ position: "absolute",
121204
+ zIndex: "999",
121205
+ background: "#666",
121206
+ opacity: "0.6",
121207
+ pointerEvents: "none"
121208
+ });
121209
+
121210
+ const root = this.getRootNode({ composed: true });
121211
+ const parent = root instanceof ShadowRoot ? root.host : this.parentElement;
121212
+
121213
+ const prev = this.previousElementSibling;
121214
+ const next = this.nextElementSibling;
121215
+
121216
+ if (!parent || !prev || !next) {
121217
+ console.warn("Spliter's parent or siblings not found.");
121218
+ return;
121219
+ }
121220
+
121221
+ (parent.shadowRoot || parent).appendChild(dragBar);
121222
+
121223
+ const dragBarOffsetParent = dragBar.offsetParent;
121224
+
121225
+ if (!dragBarOffsetParent) {
121226
+ console.error("dragBar's offsetParent could not be determined. Ensure parent or an ancestor has a position property (e.g., relative).");
121227
+ dragBar.remove();
121228
+ return;
121229
+ }
121230
+
121231
+ const dragBarOffsetParentRect = dragBarOffsetParent.getBoundingClientRect();
121232
+ const prevRect = prev.getBoundingClientRect();
121233
+ const nextRect = next.getBoundingClientRect();
121234
+
121235
+ let initialPosInOffsetParent;
121236
+ if (isHorizontal) {
121237
+ initialPosInOffsetParent = e.clientX - dragBarOffsetParentRect.left;
121238
+ dragBar.style.top = "0";
121239
+ dragBar.style.left = `${initialPosInOffsetParent}px`;
121240
+ dragBar.style.width = "1px";
121241
+ dragBar.style.height = "100%";
121242
+ } else {
121243
+ initialPosInOffsetParent = e.clientY - dragBarOffsetParentRect.top;
121244
+ dragBar.style.left = "0";
121245
+ dragBar.style.top = `${initialPosInOffsetParent}px`;
121246
+ dragBar.style.height = "1px";
121247
+ dragBar.style.width = "100%";
121248
+ }
121249
+
121250
+ const minLimit = isHorizontal
121251
+ ? prevRect.left - dragBarOffsetParentRect.left
121252
+ : prevRect.top - dragBarOffsetParentRect.top;
121253
+ const maxLimit = isHorizontal
121254
+ ? nextRect.right - dragBarOffsetParentRect.left
121255
+ : nextRect.bottom - dragBarOffsetParentRect.top;
121256
+
121257
+
121258
+ const onMove = moveEvent => {
121259
+ const clientPos = isHorizontal ? moveEvent.clientX : moveEvent.clientY;
121260
+ const currentPosInOffsetParent = isHorizontal
121261
+ ? clientPos - dragBarOffsetParentRect.left
121262
+ : clientPos - dragBarOffsetParentRect.top;
121263
+
121264
+ const clampedPos = Math.max(minLimit, Math.min(currentPosInOffsetParent, maxLimit));
121265
+
121266
+ if (isHorizontal) {
121267
+ dragBar.style.left = `${clampedPos}px`;
121268
+ } else {
121269
+ dragBar.style.top = `${clampedPos}px`;
121270
+ }
121271
+
121272
+ console.log(clampedPos);
121273
+ };
121274
+
121275
+ const onUp = (upEvent) => {
121276
+ window.removeEventListener("mousemove", onMove);
121277
+ window.removeEventListener("mouseup", onUp);
121278
+ dragBar.remove();
121279
+
121280
+ const finalDragBarPosInOffsetParent = isHorizontal
121281
+ ? parseFloat(dragBar.style.left)
121282
+ : parseFloat(dragBar.style.top);
121283
+
121284
+ // ⭐⭐ offsetParentTotalSize 변수를 여기서 정의합니다 ⭐⭐
121285
+ const offsetParentTotalSize = isHorizontal
121286
+ ? dragBarOffsetParentRect.width
121287
+ : dragBarOffsetParentRect.height;
121288
+
121289
+ const splitterThickness = isHorizontal ? splitterRect.width : splitterRect.height;
121290
+
121291
+ // prev의 시작점 (offsetParent 기준)
121292
+ const prevStartPosInOffsetParent = isHorizontal
121293
+ ? prevRect.left - dragBarOffsetParentRect.left
121294
+ : prevRect.top - dragBarOffsetParentRect.top;
121295
+
121296
+ // prev의 새로운 크기 (dragBar의 왼쪽 위치 - prev의 왼쪽 위치)
121297
+ let finalPrevSize = finalDragBarPosInOffsetParent - prevStartPosInOffsetParent;
121298
+
121299
+ // next의 새로운 크기 (offsetParent의 전체 크기 - prev 크기 - 스플리터 두께)
121300
+ // 이 방법이 전체 공간을 정확히 채우면서 오차를 마지막 패널에 몰아넣는 데 더 견고합니다.
121301
+ const MIN_PANEL_SIZE_PX = 1;
121302
+ finalPrevSize = Math.max(MIN_PANEL_SIZE_PX, finalPrevSize) - clickOffset;
121303
+ let finalNextSize = Math.max(MIN_PANEL_SIZE_PX, offsetParentTotalSize - finalPrevSize - splitterThickness);
121304
+
121305
+ // flex 속성 저장 및 복원
121306
+ const originalPrevFlex = prev.style.flex;
121307
+ const originalNextFlex = next.style.flex;
121308
+
121309
+ // 드래그 중에는 flex를 "none"으로 설정하여 직접 크기 조절
121310
+ prev.style.flex = "none";
121311
+ next.style.flex = "none";
121312
+
121313
+ console.log(finalPrevSize, finalNextSize, offsetParentTotalSize);
121314
+
121315
+ if (isHorizontal) {
121316
+ prev.style.width = `${finalPrevSize}px`;
121317
+ next.style.width = `${finalNextSize}px`;
121318
+ } else {
121319
+ prev.style.height = `${finalPrevSize}px`;
121320
+ next.style.height = `${finalNextSize}px`;
121321
+ }
121322
+
121323
+ // 작업 완료 후 원래 flex 값으로 복원
121324
+ prev.style.flex = originalPrevFlex;
121325
+ next.style.flex = originalNextFlex;
121326
+ };
121327
+
121328
+ window.addEventListener("mousemove", onMove);
121329
+ window.addEventListener("mouseup", onUp);
121330
+ };
121331
+
121332
+
121333
+ #init = () => {
121334
+ this.#detectMode(this);
121335
+
121336
+ this.classList.add(this.#mode);
121337
+
121338
+ const contents = this.innerHTML.trim();
121339
+ const gripClass = `grip-${this.#mode}`;
121340
+ const gripTmpl = (contents === "") ? `<div class="${gripClass}"></div>`: `<div class="${gripClass}"></div><div class="inner-container">${contents}</div><div class="${gripClass}"></div>`;
121341
+
121342
+ this.innerHTML = "";
121343
+ const htmlTmpl = document.createElement("template");
121344
+ htmlTmpl.innerHTML = `
121345
+ <style>
121346
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/nxSplitter.css";
121347
+ ${ninegrid.getCustomPath(this,"nxSplitter.css")}
121348
+ </style>
121349
+
121350
+ ${gripTmpl}
121351
+ `;
121352
+
121353
+ this.shadowRoot.appendChild(htmlTmpl.content.cloneNode(true));
121354
+
121355
+ this.shadowRoot.querySelectorAll(".grip-h,.grip-v").forEach(el => {
121356
+ el.addEventListener("mousedown", e => this.#startDrag(e));
121357
+ });
121358
+
121359
+ this.#prepareLayout();
121360
+
121361
+ window.addEventListener("resize", () => this.#prepareLayout());
121362
+ };
121363
+
121364
+ #isLastSplitter = () => {
121365
+ const parent = this.parentElement;
121366
+ if (!parent) return false;
121367
+
121368
+ const allSplitters = [...parent.querySelectorAll("nx-splitter")];
121369
+ return allSplitters.at(-1) === this;
121370
+ };
121371
+
121372
+ #getSiblingSizeSum = () => {
121373
+ const parent = this.parentElement;
121374
+ if (!parent) return 0;
121375
+
121376
+ const isHorizontal = this.#mode === "h";
121377
+
121378
+ return Array.from(parent.children).reduce((sum, el) => {
121379
+ const size = isHorizontal ? el.offsetWidth : el.offsetHeight;
121380
+ return sum + size;
121381
+ }, 0);
121382
+ };
121383
+
121384
+
121385
+ #prepareLayout = () => {
121386
+
121387
+ const isHorizontal = this.#mode === "h";
121388
+ const prev = this.previousElementSibling;
121389
+ const next = this.nextElementSibling;
121390
+ const parent = this.parentElement;
121391
+ if (!prev || !next || !parent) return;
121392
+
121393
+ const currentTotal = this.#getSiblingSizeSum();
121394
+ const nextTotal = isHorizontal
121395
+ ? parent.getBoundingClientRect().width
121396
+ : parent.getBoundingClientRect().height;
121397
+
121398
+ const prevSize = isHorizontal ? prev.offsetWidth : prev.offsetHeight;
121399
+ const nextSize = isHorizontal ? next.offsetWidth : next.offsetHeight;
121400
+
121401
+ const newPrevSize = nextTotal * prevSize / currentTotal;
121402
+ const newNextSize = nextTotal * nextSize / currentTotal;
121403
+
121404
+
121405
+ if (isHorizontal) {
121406
+ prev.style.width = `${newPrevSize}px`;
121407
+
121408
+ if (this.#isLastSplitter()) {
121409
+ next.style.width = `${newNextSize}px`;
121410
+ }
121411
+ } else {
121412
+ prev.style.height = `${newPrevSize}px`;
121413
+
121414
+ if (this.#isLastSplitter()) {
121415
+ next.style.height = `${newNextSize}px`;
121416
+ }
121417
+ }
121418
+ };
121419
+
121420
+ }
121421
+
121422
+ customElements.define("nx-splitter", nxSplitter);
121423
+
121424
+ class nxForm extends HTMLElement {
121425
+ #mode;
121426
+
121427
+ constructor() {
121428
+ super();
121429
+ this.attachShadow({ mode: "open" });
121430
+ }
121431
+
121432
+ connectedCallback() {
121433
+ this.#init();
121434
+ }
121435
+
121436
+ getData_BAK = () => {
121437
+ const jsonData = {};
121438
+
121439
+ this.shadowRoot.querySelectorAll("[id]").forEach(el => {
121440
+ if (el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT") {
121441
+ jsonData[el.id] = el.value;
121442
+ } else {
121443
+ jsonData[el.id] = el.textContent.trim();
121444
+ }
121445
+ });
121446
+
121447
+ return jsonData;
121448
+ };
121449
+
121450
+ getData = () => {
121451
+ const jsonData = {};
121452
+
121453
+ this.shadowRoot.querySelectorAll("input, textarea, select, [id], [name]").forEach(el => {
121454
+ const key = el.id || el.name; // id가 있으면 우선, 없으면 name 사용
121455
+ if (!key) return; // id도 name도 없으면 건너뛰기
121456
+
121457
+ const value = (el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT")
121458
+ ? el.value
121459
+ : el.textContent.trim();
121460
+
121461
+ // 중복 키를 대비한 배열 처리
121462
+ if (jsonData[key]) {
121463
+ if (!Array.isArray(jsonData[key])) {
121464
+ jsonData[key] = [jsonData[key]];
121465
+ }
121466
+ jsonData[key].push(value);
121467
+ } else {
121468
+ jsonData[key] = value;
121469
+ }
121470
+ });
121471
+
121472
+ return jsonData;
121473
+ };
121474
+
121475
+
121476
+ #init = () => {
121477
+
121478
+ const contents = this.innerHTML.trim();
121479
+
121480
+ this.innerHTML = ""; // 기존 내부 HTML 제거
121481
+ const htmlTmpl = document.createElement("template");
121482
+ htmlTmpl.innerHTML = `
121483
+ <style>
121484
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/nxForm.css";
121485
+ ${ninegrid.getCustomPath(this,"nxForm.css")}
121486
+ </style>
121487
+
121488
+ ${contents}
121489
+ `;
121490
+
121491
+ this.shadowRoot.appendChild(htmlTmpl.content.cloneNode(true));
121492
+ };
121493
+
121494
+ }
121495
+
121496
+ customElements.define("nx-form", nxForm);
121497
+
121038
121498
  class aiSettings extends HTMLElement
121039
121499
  {
121040
121500
  constructor() {
@@ -121409,6 +121869,80 @@ class aiMyMessage extends HTMLElement
121409
121869
  };
121410
121870
  }
121411
121871
 
121872
+ // aiProgressMessage 클래스 정의
121873
+ class aiProgressMessage extends HTMLElement {
121874
+ #progressData = []; // 진행 상태 데이터를 저장할 배열
121875
+ #progressElements = new Map(); // 각 진행 단계의 DOM 요소를 저장할 Map
121876
+
121877
+ constructor() {
121878
+ super();
121879
+ this.attachShadow({ mode: 'open' });
121880
+ }
121881
+
121882
+ connectedCallback() {
121883
+ this.shadowRoot.innerHTML = `
121884
+ <style>
121885
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ai.css";
121886
+ ${ninegrid.getCustomPath(this, "ai.css")}
121887
+ </style>
121888
+ <div class="chat-message progress-message">
121889
+ <ul class="progress-list"></ul>
121890
+ </div>
121891
+ `;
121892
+ }
121893
+
121894
+ /**
121895
+ * 진행 상태 데이터를 초기화하고 화면에 표시합니다.
121896
+ * @param {Array<Object>} data - [{id: 'step1', message: '분석중입니다...', completedMessage: '분석이 완료되었습니다.'}, ...]
121897
+ */
121898
+ initialize(data) {
121899
+ this.#progressData = data.map(item => ({ ...item, status: 'pending' })); // 초기 상태는 'pending'
121900
+ this.#renderProgress();
121901
+ }
121902
+
121903
+ /**
121904
+ * 특정 단계의 진행 상태를 업데이트합니다.
121905
+ * @param {string} id - 업데이트할 단계의 고유 ID
121906
+ * @param {string} status - 'completed' 또는 'pending'
121907
+ */
121908
+ updateProgress(id, status) {
121909
+ const itemIndex = this.#progressData.findIndex(item => item.id === id);
121910
+ if (itemIndex > -1) {
121911
+ this.#progressData[itemIndex].status = status;
121912
+ this.#updateProgressItem(this.#progressData[itemIndex]);
121913
+ }
121914
+ }
121915
+
121916
+ #renderProgress() {
121917
+ const progressList = this.shadowRoot.querySelector(".progress-list");
121918
+ progressList.innerHTML = ''; // 기존 목록 초기화
121919
+ this.#progressElements.clear(); // 기존 요소 Map 초기화
121920
+
121921
+ this.#progressData.forEach((item, index) => {
121922
+ const li = document.createElement("li");
121923
+ li.setAttribute("data-id", item.id);
121924
+ li.classList.add("progress-item", item.status);
121925
+ li.innerHTML = `
121926
+ <span class="icon"></span>
121927
+ <span class="text">${item.message}</span>
121928
+ `;
121929
+ progressList.appendChild(li);
121930
+ this.#progressElements.set(item.id, li); // DOM 요소 참조 저장
121931
+ });
121932
+ }
121933
+
121934
+ #updateProgressItem(item) {
121935
+ const li = this.#progressElements.get(item.id);
121936
+ if (li) {
121937
+ li.classList.remove('pending', 'completed'); // 기존 클래스 제거
121938
+ li.classList.add(item.status); // 새 상태 클래스 추가
121939
+ li.querySelector(".text").textContent = item.status === 'completed' ? item.completedMessage : item.message;
121940
+ }
121941
+ }
121942
+ }
121943
+
121944
+
121945
+
121412
121946
  class aiChat extends HTMLElement
121413
121947
  {
121414
121948
  constructor() {
@@ -121479,11 +122013,47 @@ class aiChat extends HTMLElement
121479
122013
  el.scrollIntoView({ behavior: "smooth", block: "end" });
121480
122014
  }, 200);
121481
122015
  };
122016
+
122017
+ /**
122018
+ * 챗 메시지를 추가합니다.
122019
+ * @param {string} sender - 메시지 발신자 ('me', 'ing', 'ai', 'progress')
122020
+ * @param {string} message - 메시지 내용 (progress 타입의 경우 사용되지 않음)
122021
+ * @param {Array<Object>} info - nine-grid 정보 (ai 타입에만 해당)
122022
+ * @param {Array<Object>} data - nine-grid 데이터 (ai 타입에만 해당)
122023
+ * @param {Array<string>|string} unique - nine-grid 고유 키 (ai 타입에만 해당)
122024
+ * @param {Array<Object>} progressData - aiProgressMessage의 초기 진행 데이터 (progress 타입에만 해당)
122025
+ * @returns {HTMLElement|null} 생성된 메시지 엘리먼트 (특히 aiProgressMessage의 경우 업데이트를 위해 반환)
122026
+ */
122027
+ addProgress = (progressData = null) => { // progressData 파라미터 추가
122028
+
122029
+ const target = this.shadowRoot.querySelector(".chat-body");
122030
+
122031
+ this.shadowRoot.querySelectorAll("nx-ai-ing-message").forEach(el => {
122032
+ el.remove();
122033
+ });
122034
+
122035
+ let el = document.createElement("nx-ai-progress-message");
122036
+ if (progressData) {
122037
+ el.initialize(progressData); // progressData로 초기화
122038
+ }
122039
+ target.appendChild(el);
122040
+
122041
+ setTimeout(() => {
122042
+ if (el) { // el이 null이 아닐 때만 scrollIntoView 호출
122043
+ el.scrollIntoView({ behavior: "smooth", block: "end" });
122044
+ }
122045
+ }, 200);
122046
+
122047
+ return el; // 생성된 엘리먼트를 반환
122048
+ };
121482
122049
  }
121483
122050
 
122051
+
122052
+
121484
122053
  customElements.define("nx-ai-message", aiMessage);
121485
122054
  customElements.define("nx-ai-ing-message", aiIngMessage);
121486
122055
  customElements.define("nx-ai-my-message", aiMyMessage);
122056
+ customElements.define("nx-ai-progress-message", aiProgressMessage);
121487
122057
  customElements.define("nx-ai-chat", aiChat);
121488
122058
 
121489
122059
  /**
@@ -201851,351 +202421,6 @@ class IdeAssi extends HTMLElement
201851
202421
  //this.shadowRoot.querySelector('ide-diff-popup').remove();
201852
202422
 
201853
202423
  this.shadowRoot.appendChild(document.createElement('ide-diff-popup'));
201854
-
201855
- //setTimeout(() => {
201856
- let src1 = `
201857
- import React, { useRef, useEffect } from "react" adfa;
201858
- import { api, ai } from "ide-assi";
201859
- import ninegrid from "ninegrid2";
201860
-
201861
- const DocManager = () => {
201862
- const tabRef = useRef(null);
201863
- const gridRef = useRef(null);
201864
-
201865
- const selectList = async (params) => {
201866
- if (!gridRef.current) return;
201867
- gridRef.current.classList.add("loading");
201868
- api.post(\`/api/tmpl-a/doc-manager/selectList\`, params).then((res) => {
201869
- gridRef.current.data.source = res.list;
201870
- });
201871
- };
201872
-
201873
- const handleNaturalLanguageSearch = async () => {
201874
- const searchTextElement = ninegrid.querySelector("#searchText", tabRef.current);
201875
- const searchText = searchTextElement ? searchTextElement.value : "";
201876
-
201877
- if (!gridRef.current) return;
201878
- gridRef.current.classList.add("loading");
201879
-
201880
- let params = {};
201881
- if (searchText) {
201882
- params = {
201883
- _whereClause: await ai.generateWhereCause(
201884
- "tmpla/DocManagerMapper.xml",
201885
- "selectList",
201886
- searchText,
201887
- import.meta.env.VITE_GEMINI_API_KEY
201888
- ),
201889
- };
201890
- }
201891
- selectList(params);
201892
- };
201893
-
201894
- const handleClassicSearch = () => {
201895
- if (!gridRef.current) return;
201896
- gridRef.current.classList.add("loading");
201897
-
201898
- const form2Element = ninegrid.querySelector(".form2", tabRef.current);
201899
- const params = form2Element ? form2Element.getData() : {};
201900
- selectList(params);
201901
- };
201902
-
201903
- useEffect(() => {
201904
- selectList({});
201905
-
201906
- const searchTextElement = ninegrid.querySelector("#searchText", tabRef.current);
201907
- const searchButton = ninegrid.querySelector(".search", tabRef.current);
201908
-
201909
- const handleKeyDown = (e) => {
201910
- if (e.key === "Enter" && !e.isComposing) {
201911
- handleNaturalLanguageSearch();
201912
- }
201913
- };
201914
-
201915
- const handleClick = () => {
201916
- handleClassicSearch();
201917
- };
201918
-
201919
- if (searchTextElement) {
201920
- searchTextElement.addEventListener("keydown", handleKeyDown);
201921
- }
201922
- if (searchButton) {
201923
- searchButton.addEventListener("click", handleClick);
201924
- }
201925
-
201926
- return () => {
201927
- if (searchTextElement) {
201928
- searchTextElement.removeEventListener("keydown", handleKeyDown);
201929
- }
201930
- if (searchButton) {
201931
- searchButton.removeEventListener("click", handleClick);
201932
- }
201933
- };
201934
- }, []);
201935
-
201936
- return (
201937
- <div className="wrapper">
201938
- <nx-collapse target="nx-tab" className="search-collapse"></nx-collapse>
201939
- <div className="list-wrapper">
201940
- <nx-tab theme="theme-3" ref={tabRef}>
201941
- <nx-tab-page caption="자연어 검색">
201942
- <nx-form className="form1">
201943
- <input
201944
- type="text"
201945
- id="searchText"
201946
- name="searchText"
201947
- placeholder="자연어 검색어를 입력하세요 (ex: 작성자가 홍길동인 데이타를 찾아줘)"
201948
- />
201949
- </nx-form>
201950
- </nx-tab-page>
201951
- <nx-tab-page caption="클래식 검색">
201952
- <nx-form className="form2">
201953
- <label>문서명: <input type="text" name="docNm" /></label>
201954
- <label>매출액:
201955
- <input type="number" name="minAmt" placeholder="최소" /> ~
201956
- <input type="number" name="maxAmt" placeholder="최대" />
201957
- </label>
201958
- </nx-form>
201959
- <button className="search">검색</button>
201960
- </nx-tab-page>
201961
- </nx-tab>
201962
-
201963
- <div className="grid-wrapper">
201964
- <nine-grid
201965
- ref={gridRef}
201966
- caption="문서관리"
201967
- select-type="row"
201968
- show-title-bar="true"
201969
- show-menu-icon="true"
201970
- show-status-bar="true"
201971
- enable-fixed-col="true"
201972
- row-resizable="false"
201973
- col-movable="true"
201974
- >
201975
- <table>
201976
- <colgroup>
201977
- <col width="50" fixed="left" background-color="gray" />
201978
- <col width="100" />
201979
- <col width="100" />
201980
- <col width="200" />
201981
- <col width="120" />
201982
- <col width="100" />
201983
- <col width="150" />
201984
- <col width="150" />
201985
- </colgroup>
201986
- <thead>
201987
- <tr>
201988
- <th>No.</th>
201989
- <th>최종수정자</th>
201990
- <th>문서ID</th>
201991
- <th>문서명</th>
201992
- <th>매출액</th>
201993
- <th>최초등록자</th>
201994
- <th>최초등록일</th>
201995
- <th>최종수정일</th>
201996
- </tr>
201997
- </thead>
201998
- <tbody>
201999
- <tr>
202000
- <th><ng-row-indicator /></th>
202001
- <td data-bind="updateUser" text-align="center"></td>
202002
- <td data-bind="docId" text-align="center"></td>
202003
- <td data-bind="docNm" text-align="left"></td>
202004
- <td
202005
- data-bind="amt"
202006
- data-expr="data.amt.toLocaleString()"
202007
- text-align="right"
202008
- show-icon="true"
202009
- icon-type="sphere"
202010
- icon-color="data.amt >= 2000 ? 'red' : 'gray'"
202011
- ></td>
202012
- <td data-bind="insertUser" text-align="center"></td>
202013
- <td data-bind="insertDt" text-align="center"></td>
202014
- <td data-bind="updateDt" text-align="center"></td>
202015
- </tr>
202016
- </tbody>
202017
- </table>
202018
- </nine-grid>
202019
- </div>
202020
- </div>
202021
- </div>
202022
- );
202023
- };
202024
-
202025
- export default DocManager;
202026
- `;
202027
-
202028
- let src2 = `
202029
- import React, { useRef, useEffect } from "react";
202030
- import { api, ai } from "ide-assi";
202031
- import ninegrid from "ninegrid2";
202032
-
202033
- const DocManager = () => {
202034
- const tabRef = useRef(null);
202035
- const gridRef = useRef(null);
202036
-
202037
- const selectList = async (params) => {
202038
- if (!gridRef.current) return;
202039
- gridRef.current.classList.add("loading");
202040
- api.post(\`/api/tmpl-a/doc-manager/selectList\`, params).then((res) => {
202041
- gridRef.current.data.source = res.list;
202042
- });
202043
- };
202044
-
202045
- const handleNaturalLanguageSearch = async () => {
202046
- const searchTextElement = ninegrid.querySelector("#searchText", tabRef.current);
202047
- const searchText = searchTextElement ? searchTextElement.value : "";
202048
-
202049
- if (!gridRef.current) return;
202050
- gridRef.current.classList.add("loading");
202051
-
202052
- let params = {};
202053
- if (searchText) {
202054
- params = {
202055
- _whereClause: await ai.generateWhereCause(
202056
- "tmpla/DocManagerMapper.xml",
202057
- "selectList",
202058
- searchText,
202059
- import.meta.env.VITE_GEMINI_API_KEY
202060
- ),
202061
- };
202062
- }
202063
- selectList(params);
202064
- };
202065
-
202066
- const handleClassicSearch = () => {
202067
- if (!gridRef.current) return;
202068
- gridRef.current.classList.add("loading");
202069
-
202070
- const form2Element = ninegrid.querySelector(".form2", tabRef.current);
202071
- const params = form2Element ? form2Element.getData() : {};
202072
- selectList(params);
202073
- };
202074
-
202075
- useEffect(() => {
202076
- selectList({});
202077
-
202078
- const searchTextElement = ninegrid.querySelector("#searchText", tabRef.current);
202079
- const searchButton = ninegrid.querySelector(".search", tabRef.current);
202080
-
202081
- const handleKeyDown = (e) => {
202082
- if (e.key === "Enter" && !e.isComposing) {
202083
- handleNaturalLanguageSearch();
202084
- }
202085
- };
202086
-
202087
- const handleClick = () => {
202088
- handleClassicSearch();
202089
- };
202090
-
202091
- if (searchTextElement) {
202092
- searchTextElement.addEventListener("keydown", handleKeyDown);
202093
- }
202094
- if (searchButton) {
202095
- searchButton.addEventListener("click", handleClick);
202096
- }
202097
-
202098
- return () => {
202099
- if (searchTextElement) {
202100
- searchTextElement.removeEventListener("keydown", handleKeyDown);
202101
- }
202102
- if (searchButton) {
202103
- searchButton.removeEventListener("click", handleClick);
202104
- }
202105
- };
202106
- }, []);
202107
-
202108
- return (
202109
- <div className="wrapper">
202110
- <nx-collapse target="nx-tab" className="search-collapse"></nx-collapse>
202111
- <div className="list-wrapper">
202112
- <nx-tab theme="theme-3" ref={tabRef}>
202113
- <nx-tab-page caption="자연어 검색">
202114
- <nx-form className="form1">
202115
- <input
202116
- type="text"
202117
- id="searchText"
202118
- name="searchText"
202119
- placeholder="자연어 검색어를 입력하세요 (ex: 작성자가 홍길동인 데이타를 찾아줘)"
202120
- />
202121
- </nx-form>
202122
- </nx-tab-page>
202123
- <nx-tab-page caption="클래식 검색">
202124
- <nx-form className="form2">
202125
- <label>문서명: <input type="text" name="docNm" /></label>
202126
- <label>매출액:
202127
- <input type="number" name="minAmt" placeholder="최소" /> ~
202128
- <input type="number" name="maxAmt" placeholder="최대" />
202129
- </label>
202130
- </nx-form>
202131
- <button className="search">검색</button>
202132
- </nx-tab-page>
202133
- </nx-tab>
202134
-
202135
- <div className="grid-wrapper">
202136
- <nine-grid
202137
- ref={gridRef}
202138
- caption="매출 문서 관리"
202139
- select-type="row"
202140
- show-title-bar="true"
202141
- show-menu-icon="true"
202142
- show-status-bar="true"
202143
- enable-fixed-col="true"
202144
- row-resizable="false"
202145
- col-movable="true"
202146
- >
202147
- <table>
202148
- <colgroup>
202149
- <col width="50" fixed="left" background-color="gray" />
202150
- <col width="100" />
202151
- <col width="120" />
202152
- <col width="100" />
202153
- <col width="200" />
202154
- <col width="100" />
202155
- <col width="150" />
202156
- <col width="150" />
202157
- </colgroup>
202158
- <thead>
202159
- <tr>
202160
- <th>No.</th>
202161
- <th>문서ID</th>
202162
- <th>매출액</th>
202163
- <th>최종수정자</th>
202164
- <th>문서명</th>
202165
- <th>최초등록자</th>
202166
- <th>최초등록일</th>
202167
- <th>최종수정일</th>
202168
- </tr>
202169
- </thead>
202170
- <tbody>
202171
- <tr>
202172
- <th><ng-row-indicator /></th>
202173
- <td data-bind="docId" text-align="center"></td>
202174
- <td
202175
- data-bind="amt"
202176
- data-expr="data.amt.toLocaleString()"
202177
- text-align="right"
202178
- show-icon="true"
202179
- icon-type="sphere"
202180
- icon-color="data.amt >= 2000 ? 'red' : 'gray'"
202181
- ></td>
202182
- <td data-bind="updateUser" text-align="center"></td>
202183
- <td data-bind="docNm" text-align="left"></td>
202184
- <td data-bind="insertUser" text-align="center"></td>
202185
- <td data-bind="insertDt" text-align="center"></td>
202186
- <td data-bind="updateDt" text-align="center"></td>
202187
- </tr>
202188
- </tbody>
202189
- </table>
202190
- </nine-grid>
202191
- </div>
202192
- </div>
202193
- </div>
202194
- );
202195
- };
202196
-
202197
- export default DocManager1;
202198
- `;
202199
202424
  /**
202200
202425
  src1 = `
202201
202426
  <tr>
@@ -202222,9 +202447,48 @@ export default DocManager1;
202222
202447
  </tr>
202223
202448
  `; */
202224
202449
 
202225
- this.shadowRoot.querySelector("ide-diff-popup").popup(src1, src2);
202450
+ //this.shadowRoot.querySelector("ide-diff-popup").popup(src1, src2);
202451
+
202452
+ //return;
202453
+
202454
+
202455
+ // 1. 초기 진행 상태 메시지 추가
202456
+ const initialProgressData = [
202457
+ { id: 'analysis', message: '1. 분석중입니다...', completedMessage: '1-1. 분석이 완료되었습니다.' },
202458
+ { id: 'design', message: '2. 설계중...', completedMessage: '2-1. 설계가 완료되었습니다.' },
202459
+ { id: 'development', message: '3. 개발중...', completedMessage: '3-1. 개발이 완료되었습니다.' }
202460
+ ];
202461
+
202462
+ // aiChat.add 호출 시, progressData를 마지막 인자로 전달하고 생성된 엘리먼트를 받습니다.
202463
+ const progressMessageInstance = elAiChat.addProgress(initialProgressData);
202464
+
202465
+ // 2. 시간이 지난 후, 특정 단계의 완료를 알림
202466
+ // 예를 들어, 3초 후에 분석 완료
202467
+ setTimeout(() => {
202468
+ if (progressMessageInstance) {
202469
+ progressMessageInstance.updateProgress('analysis', 'completed');
202470
+ console.log("분석 완료 메시지 업데이트");
202471
+ }
202472
+ }, 3000);
202473
+
202474
+ // 예를 들어, 6초 후에 설계 완료
202475
+ setTimeout(() => {
202476
+ if (progressMessageInstance) {
202477
+ progressMessageInstance.updateProgress('design', 'completed');
202478
+ console.log("설계 완료 메시지 업데이트");
202479
+ }
202480
+ }, 6000);
202481
+
202482
+ // 모든 단계가 완료된 후, 일반 AI 메시지 추가 (선택 사항)
202483
+ setTimeout(() => {
202484
+ elAiChat.add("ai", "모든 작업이 성공적으로 완료되었습니다!", [], []);
202485
+ }, 9000);
202486
+
202487
+
202226
202488
 
202227
202489
  return;
202490
+
202491
+ const elAiChat = this.shadowRoot.querySelector("nx-ai-chat");
202228
202492
  }
202229
202493
 
202230
202494
  #toggleCollapseHandler = () => {