ide-assi 0.528.0 → 0.530.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.
@@ -1,43 +1,108 @@
1
1
  import ninegrid from "ninegrid2";
2
+ import './ideLoadingTips.js'; // aiLoadingTips 컴포넌트 정의 파일 임포트
3
+
4
+ class IdeTipPopup extends HTMLElement {
5
+ #ideLoadingTipsInstance = null; // aiLoadingTips 인스턴스를 저장할 변수
6
+ #tipsUrl = '/data/tips.json'; // 팁 JSON 파일의 기본 경로 (public 폴더 내)
7
+ #tipCategory = 'componentTips'; // 기본으로 표시할 팁 카테고리
2
8
 
3
- class IdeTipPopup extends HTMLElement
4
- {
5
9
  constructor() {
6
-
7
10
  super();
8
11
  this.attachShadow({ mode: 'open' });
9
12
  }
10
13
 
11
14
  connectedCallback() {
12
-
13
15
  this.shadowRoot.innerHTML = `
14
- <style>
15
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideAssi.css";
16
- ${ninegrid.getCustomPath(this,"ideAssi.css")}
17
- </style>
18
-
19
- <nx-dialog>
20
- <div class="buttons">
21
- adfa
22
- </div>
23
-
24
- </nx-dialog>
25
- `;
16
+ <style>
17
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideAssi.css";
18
+ ${ninegrid.getCustomPath(this,"ideAssi.css")}
19
+
20
+ /* ide-tip-popup 전용 스타일 (필요시 추가) */
21
+ .dialog-header {
22
+ font-size: 1.2em;
23
+ font-weight: bold;
24
+ margin-bottom: 15px;
25
+ text-align: center;
26
+ color: #333;
27
+ padding-top: 10px; /* 헤더 위 공간 */
28
+ }
29
+ .dialog-content {
30
+ /* aiLoadingTips가 들어갈 컨테이너 */
31
+ padding: 0 5px;
32
+ }
33
+ /* nx-dialog의 닫기 버튼과 겹치지 않도록 조정 */
34
+ nx-dialog::part(close-button) { /* nx-dialog의 내부 닫기 버튼에 접근 */
35
+ top: 10px;
36
+ right: 10px;
37
+ }
38
+ </style>
39
+
40
+ <nx-dialog>
41
+ <div slot="header" class="dialog-header">AI가 답변을 준비 중입니다...</div>
42
+ <div class="dialog-content"></div>
43
+ <div slot="footer" class="dialog-footer"></div>
44
+ </nx-dialog>
45
+ `;
26
46
 
27
47
  this.#init();
28
48
  }
29
49
 
30
50
  #init = () => {
31
-
51
+ // nx-dialog가 DOM에 추가된 후 내부 요소에 접근
52
+ const dialog = this.shadowRoot.querySelector('nx-dialog');
53
+ if (dialog) {
54
+ // dialog가 닫힐 때 aiLoadingTips의 애니메이션 중지
55
+ dialog.addEventListener('close', () => {
56
+ if (this.#aiLoadingTipsInstance) {
57
+ this.#aiLoadingTipsInstance.stopTips();
58
+ // 팝업이 닫힐 때 aiLoadingTips 인스턴스 제거 (선택 사항)
59
+ this.#aiLoadingTipsInstance.remove();
60
+ this.#aiLoadingTipsInstance = null;
61
+ }
62
+ });
63
+ }
32
64
  }
33
65
 
34
- popup = () => {
66
+ /**
67
+ * 팁 팝업을 표시하고 aiLoadingTips를 시작합니다.
68
+ * @param {string} [category='componentTips'] - 표시할 팁의 카테고리 ('componentTips' 또는 'generalAITips')
69
+ * @param {string} [tipsUrl='/data/tips.json'] - 팁 JSON 파일의 URL
70
+ */
71
+ async popup(category = 'componentTips', tipsUrl = '/tips/tip.json') {
72
+ this.#tipCategory = category;
73
+ this.#tipsUrl = tipsUrl;
74
+
75
+ const dialogContent = this.shadowRoot.querySelector('.dialog-content');
76
+ if (!dialogContent) {
77
+ console.error("Popup content container not found.");
78
+ return;
79
+ }
80
+
81
+ // 기존 aiLoadingTips 인스턴스가 있다면 제거
82
+ if (this.#ideLoadingTipsInstance) {
83
+ this.#ideLoadingTipsInstance.stopTips();
84
+ this.#ideLoadingTipsInstance.remove();
85
+ this.#ideLoadingTipsInstance = null;
86
+ }
87
+
88
+ // 새로운 aiLoadingTips 인스턴스 생성 및 추가
89
+ this.#ideLoadingTipsInstance = document.createElement('ide-loading-tips');
90
+ dialogContent.appendChild(this.#ideLoadingTipsInstance);
91
+
92
+ // 팁 로딩 시작
93
+ await this.#ideLoadingTipsInstance.startTips(this.#tipsUrl, this.#tipCategory);
94
+
95
+ // nx-dialog 팝업 표시
35
96
  this.shadowRoot.querySelector('nx-dialog')?.showModal();
36
97
  };
37
98
 
99
+ /**
100
+ * 팁 팝업을 닫습니다.
101
+ */
38
102
  close = () => {
39
103
  this.shadowRoot.querySelector('nx-dialog')?.close();
104
+ // close 이벤트 리스너에서 aiLoadingTips 정리 로직이 실행될 것임
40
105
  };
41
106
  }
42
107
 
43
- customElements.define("ide-tip-popup", IdeTipPopup);
108
+ customElements.define("ide-tip-popup", IdeTipPopup);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ide-assi",
3
3
  "type": "module",
4
- "version": "0.528.0",
4
+ "version": "0.530.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {
@@ -0,0 +1,259 @@
1
+ // aiLoadingTips.js
2
+ // This file contains the full source code for the nx-ai-loading-tips web component.
3
+
4
+ import ninegrid from "ninegrid2"; // Assuming ninegrid2 is available in your project
5
+
6
+ class IdeLoadingTips extends HTMLElement {
7
+ #tips = []; // Stores tip objects {text: string, images: string[]}
8
+ #currentTipIndex = 0; // Index of the currently displayed tip
9
+ #currentImageIndex = 0; // Index of the currently displayed image for the current tip
10
+ #tipIntervalId = null; // Interval ID for cycling through tips
11
+ #imageIntervalId = null; // Interval ID for cycling through images within a tip
12
+ #tipDisplayDuration = 5000; // How long each tip is displayed (5 seconds)
13
+ #imageDisplayDuration = 2000; // How long each image within a tip is displayed (2 seconds)
14
+
15
+ // DOM element references (cached for performance)
16
+ tipElement = null;
17
+ loadingAnimation = null;
18
+ tipImageElement = null;
19
+ tipImageContainer = null;
20
+
21
+ constructor() {
22
+ super();
23
+ this.attachShadow({ mode: 'open' });
24
+ }
25
+
26
+ connectedCallback() {
27
+ // Set up the initial structure of the component in its Shadow DOM
28
+ this.shadowRoot.innerHTML = `
29
+ <style>
30
+ /* Import ninegrid's base AI CSS */
31
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ai.css";
32
+ /* Import custom CSS specific to this component (adjust path as needed) */
33
+ ${ninegrid.getCustomPath(this, "ai.css")}
34
+
35
+ /* Component-specific styles */
36
+ :host {
37
+ display: flex;
38
+ justify-content: flex-start;
39
+ padding: 5px;
40
+ flex-direction: column;
41
+ }
42
+
43
+ .chat-message.loading-tips-message {
44
+ max-width: 80%;
45
+ border-radius: 8px;
46
+ font-size: 14px;
47
+ background-color: #fff;
48
+ color: black;
49
+ align-self: flex-start;
50
+ text-align: left;
51
+ position: relative;
52
+ box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.15);
53
+ padding: 12px 16px;
54
+ display: flex;
55
+ flex-direction: column;
56
+ align-items: flex-start;
57
+ padding-bottom: 8px; /* Adjusted for image container */
58
+ }
59
+
60
+ .loading-animation {
61
+ width: 20px;
62
+ height: 20px;
63
+ border: 3px solid #f3f3f3;
64
+ border-top: 3px solid #3498db;
65
+ border-radius: 50%;
66
+ animation: spin 1s linear infinite;
67
+ margin-bottom: 8px;
68
+ display: none; /* Controlled by JS */
69
+ }
70
+
71
+ .tip-content {
72
+ width: 100%;
73
+ }
74
+
75
+ .tip-label {
76
+ font-size: 0.85em;
77
+ color: #666;
78
+ margin: 0 0 5px 0;
79
+ font-weight: bold;
80
+ }
81
+
82
+ .current-tip {
83
+ font-size: 1em;
84
+ margin: 0;
85
+ color: #333;
86
+ line-height: 1.5;
87
+ margin-bottom: 10px; /* Space between text and image */
88
+ }
89
+
90
+ .tip-image-container {
91
+ width: 100%;
92
+ max-height: 150px; /* Max height for the image area */
93
+ overflow: hidden;
94
+ display: flex;
95
+ justify-content: center;
96
+ align-items: center;
97
+ background-color: #f0f0f0; /* Optional background for image area */
98
+ border-radius: 4px;
99
+ margin-top: 5px;
100
+ display: none; /* Controlled by JS */
101
+ }
102
+
103
+ .tip-image {
104
+ max-width: 100%;
105
+ max-height: 100%;
106
+ object-fit: contain; /* Ensures image fits without cropping, maintaining aspect ratio */
107
+ border-radius: 4px;
108
+ display: none; /* Controlled by JS */
109
+ }
110
+
111
+ @keyframes spin {
112
+ 0% { transform: rotate(0deg); }
113
+ 100% { transform: rotate(360deg); }
114
+ }
115
+ </style>
116
+ <div class="chat-message loading-tips-message">
117
+ <div class="loading-animation"></div>
118
+ <div class="tip-content">
119
+ <p class="tip-label">오늘의 AI 팁:</p>
120
+ <p class="current-tip"></p>
121
+ <div class="tip-image-container">
122
+ <img class="tip-image" src="" alt="팁 관련 이미지">
123
+ </div>
124
+ </div>
125
+ </div>
126
+ `;
127
+ // Cache DOM element references
128
+ this.tipElement = this.shadowRoot.querySelector('.current-tip');
129
+ this.loadingAnimation = this.shadowRoot.querySelector('.loading-animation');
130
+ this.tipImageElement = this.shadowRoot.querySelector('.tip-image');
131
+ this.tipImageContainer = this.shadowRoot.querySelector('.tip-image-container');
132
+ }
133
+
134
+ disconnectedCallback() {
135
+ // Clean up all intervals when the component is removed from the DOM
136
+ this.stopTips();
137
+ }
138
+
139
+ /**
140
+ * Loads tips asynchronously from a JSON URL and starts displaying them.
141
+ * @param {string} tipsUrl - The URL of the JSON file containing tip data.
142
+ * @param {string} tipCategory - The key in the JSON file's data that corresponds to the desired tip array (e.g., 'componentTips').
143
+ */
144
+ async startTips(tipsUrl, tipCategory) {
145
+ if (!tipsUrl || !tipCategory) {
146
+ console.error("Tips URL or category not provided for aiLoadingTips.");
147
+ return;
148
+ }
149
+
150
+ try {
151
+ const response = await fetch(tipsUrl);
152
+ if (!response.ok) {
153
+ throw new Error(`Failed to fetch tips: ${response.statusText}`);
154
+ }
155
+ const data = await response.json();
156
+ const tips = data[tipCategory];
157
+
158
+ if (!tips || tips.length === 0) {
159
+ console.warn(`No tips found for category '${tipCategory}' in ${tipsUrl}.`);
160
+ return;
161
+ }
162
+
163
+ this.#tips = tips;
164
+ this.#currentTipIndex = 0;
165
+ this.stopTips(); // Stop any existing intervals before starting new ones
166
+
167
+ this.showCurrentTip(); // Display the first tip immediately
168
+
169
+ // Start interval for cycling through tips
170
+ this.#tipIntervalId = setInterval(() => {
171
+ this.showNextTip();
172
+ }, this.#tipDisplayDuration);
173
+
174
+ this.loadingAnimation.style.display = 'block'; // Show the loading spinner
175
+ } catch (error) {
176
+ console.error("Error loading tips:", error);
177
+ this.stopTips(); // Stop intervals and hide animations on error
178
+ if (this.tipElement) {
179
+ this.tipElement.textContent = "팁을 불러오는 중 오류가 발생했습니다.";
180
+ }
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Stops all tip and image display intervals and hides related UI elements.
186
+ */
187
+ stopTips() {
188
+ if (this.#tipIntervalId) {
189
+ clearInterval(this.#tipIntervalId);
190
+ this.#tipIntervalId = null;
191
+ }
192
+ if (this.#imageIntervalId) {
193
+ clearInterval(this.#imageIntervalId);
194
+ this.#imageIntervalId = null;
195
+ }
196
+ this.loadingAnimation.style.display = 'none';
197
+ this.tipImageElement.style.display = 'none'; // Hide image
198
+ this.tipImageElement.src = ''; // Clear image source
199
+ this.tipImageContainer.style.display = 'none'; // Hide image container
200
+ }
201
+
202
+ /**
203
+ * Advances to the next tip and calls showCurrentTip() to display it.
204
+ */
205
+ showNextTip() {
206
+ this.#currentTipIndex = (this.#currentTipIndex + 1) % this.#tips.length;
207
+ this.showCurrentTip();
208
+ }
209
+
210
+ /**
211
+ * Displays the current tip's text and manages its associated images.
212
+ */
213
+ showCurrentTip() {
214
+ const currentTip = this.#tips[this.#currentTipIndex];
215
+ if (this.tipElement && currentTip) {
216
+ this.tipElement.textContent = currentTip.text;
217
+
218
+ // Clear any existing image cycling interval for the previous tip
219
+ if (this.#imageIntervalId) {
220
+ clearInterval(this.#imageIntervalId);
221
+ this.#imageIntervalId = null;
222
+ }
223
+
224
+ // Image handling logic
225
+ if (currentTip.images && currentTip.images.length > 0) {
226
+ this.#currentImageIndex = 0; // Reset image index for the new tip
227
+ this.showCurrentImage(currentTip.images[this.#currentImageIndex]);
228
+ this.tipImageElement.style.display = 'block'; // Show the image
229
+ this.tipImageContainer.style.display = 'flex'; // Show the image container
230
+
231
+ if (currentTip.images.length > 1) {
232
+ // If there's more than one image, start cycling through them
233
+ this.#imageIntervalId = setInterval(() => {
234
+ this.#currentImageIndex = (this.#currentImageIndex + 1) % currentTip.images.length;
235
+ this.showCurrentImage(currentTip.images[this.#currentImageIndex]);
236
+ }, this.#imageDisplayDuration);
237
+ }
238
+ } else {
239
+ // If no images are provided for this tip, hide the image elements
240
+ this.tipImageElement.style.display = 'none';
241
+ this.tipImageContainer.style.display = 'none';
242
+ this.tipImageElement.src = ''; // Clear image source
243
+ }
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Sets the source of the image element to display the given image.
249
+ * @param {string} imageUrl - The URL of the image to display.
250
+ */
251
+ showCurrentImage(imageUrl) {
252
+ if (this.tipImageElement) {
253
+ this.tipImageElement.src = imageUrl;
254
+ }
255
+ }
256
+ }
257
+
258
+ // Define the custom element
259
+ customElements.define("ide-loading-tips", IdeLoadingTips);
@@ -1,43 +1,108 @@
1
1
  import ninegrid from "ninegrid2";
2
+ import './ideLoadingTips.js'; // aiLoadingTips 컴포넌트 정의 파일 임포트
3
+
4
+ class IdeTipPopup extends HTMLElement {
5
+ #ideLoadingTipsInstance = null; // aiLoadingTips 인스턴스를 저장할 변수
6
+ #tipsUrl = '/data/tips.json'; // 팁 JSON 파일의 기본 경로 (public 폴더 내)
7
+ #tipCategory = 'componentTips'; // 기본으로 표시할 팁 카테고리
2
8
 
3
- class IdeTipPopup extends HTMLElement
4
- {
5
9
  constructor() {
6
-
7
10
  super();
8
11
  this.attachShadow({ mode: 'open' });
9
12
  }
10
13
 
11
14
  connectedCallback() {
12
-
13
15
  this.shadowRoot.innerHTML = `
14
- <style>
15
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideAssi.css";
16
- ${ninegrid.getCustomPath(this,"ideAssi.css")}
17
- </style>
18
-
19
- <nx-dialog>
20
- <div class="buttons">
21
- adfa
22
- </div>
23
-
24
- </nx-dialog>
25
- `;
16
+ <style>
17
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideAssi.css";
18
+ ${ninegrid.getCustomPath(this,"ideAssi.css")}
19
+
20
+ /* ide-tip-popup 전용 스타일 (필요시 추가) */
21
+ .dialog-header {
22
+ font-size: 1.2em;
23
+ font-weight: bold;
24
+ margin-bottom: 15px;
25
+ text-align: center;
26
+ color: #333;
27
+ padding-top: 10px; /* 헤더 위 공간 */
28
+ }
29
+ .dialog-content {
30
+ /* aiLoadingTips가 들어갈 컨테이너 */
31
+ padding: 0 5px;
32
+ }
33
+ /* nx-dialog의 닫기 버튼과 겹치지 않도록 조정 */
34
+ nx-dialog::part(close-button) { /* nx-dialog의 내부 닫기 버튼에 접근 */
35
+ top: 10px;
36
+ right: 10px;
37
+ }
38
+ </style>
39
+
40
+ <nx-dialog>
41
+ <div slot="header" class="dialog-header">AI가 답변을 준비 중입니다...</div>
42
+ <div class="dialog-content"></div>
43
+ <div slot="footer" class="dialog-footer"></div>
44
+ </nx-dialog>
45
+ `;
26
46
 
27
47
  this.#init();
28
48
  }
29
49
 
30
50
  #init = () => {
31
-
51
+ // nx-dialog가 DOM에 추가된 후 내부 요소에 접근
52
+ const dialog = this.shadowRoot.querySelector('nx-dialog');
53
+ if (dialog) {
54
+ // dialog가 닫힐 때 aiLoadingTips의 애니메이션 중지
55
+ dialog.addEventListener('close', () => {
56
+ if (this.#aiLoadingTipsInstance) {
57
+ this.#aiLoadingTipsInstance.stopTips();
58
+ // 팝업이 닫힐 때 aiLoadingTips 인스턴스 제거 (선택 사항)
59
+ this.#aiLoadingTipsInstance.remove();
60
+ this.#aiLoadingTipsInstance = null;
61
+ }
62
+ });
63
+ }
32
64
  }
33
65
 
34
- popup = () => {
66
+ /**
67
+ * 팁 팝업을 표시하고 aiLoadingTips를 시작합니다.
68
+ * @param {string} [category='componentTips'] - 표시할 팁의 카테고리 ('componentTips' 또는 'generalAITips')
69
+ * @param {string} [tipsUrl='/data/tips.json'] - 팁 JSON 파일의 URL
70
+ */
71
+ async popup(category = 'componentTips', tipsUrl = '/tips/tip.json') {
72
+ this.#tipCategory = category;
73
+ this.#tipsUrl = tipsUrl;
74
+
75
+ const dialogContent = this.shadowRoot.querySelector('.dialog-content');
76
+ if (!dialogContent) {
77
+ console.error("Popup content container not found.");
78
+ return;
79
+ }
80
+
81
+ // 기존 aiLoadingTips 인스턴스가 있다면 제거
82
+ if (this.#ideLoadingTipsInstance) {
83
+ this.#ideLoadingTipsInstance.stopTips();
84
+ this.#ideLoadingTipsInstance.remove();
85
+ this.#ideLoadingTipsInstance = null;
86
+ }
87
+
88
+ // 새로운 aiLoadingTips 인스턴스 생성 및 추가
89
+ this.#ideLoadingTipsInstance = document.createElement('ide-loading-tips');
90
+ dialogContent.appendChild(this.#ideLoadingTipsInstance);
91
+
92
+ // 팁 로딩 시작
93
+ await this.#ideLoadingTipsInstance.startTips(this.#tipsUrl, this.#tipCategory);
94
+
95
+ // nx-dialog 팝업 표시
35
96
  this.shadowRoot.querySelector('nx-dialog')?.showModal();
36
97
  };
37
98
 
99
+ /**
100
+ * 팁 팝업을 닫습니다.
101
+ */
38
102
  close = () => {
39
103
  this.shadowRoot.querySelector('nx-dialog')?.close();
104
+ // close 이벤트 리스너에서 aiLoadingTips 정리 로직이 실행될 것임
40
105
  };
41
106
  }
42
107
 
43
- customElements.define("ide-tip-popup", IdeTipPopup);
108
+ customElements.define("ide-tip-popup", IdeTipPopup);