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.
@@ -202898,43 +202898,366 @@ class ideAssiSettings extends HTMLElement
202898
202898
 
202899
202899
  customElements.define("ide-assi-settings", ideAssiSettings);
202900
202900
 
202901
- class IdeTipPopup extends HTMLElement
202902
- {
202901
+ // aiLoadingTips.js
202902
+ // This file contains the full source code for the nx-ai-loading-tips web component.
202903
+
202904
+
202905
+ class IdeLoadingTips extends HTMLElement {
202906
+ #tips = []; // Stores tip objects {text: string, images: string[]}
202907
+ #currentTipIndex = 0; // Index of the currently displayed tip
202908
+ #currentImageIndex = 0; // Index of the currently displayed image for the current tip
202909
+ #tipIntervalId = null; // Interval ID for cycling through tips
202910
+ #imageIntervalId = null; // Interval ID for cycling through images within a tip
202911
+ #tipDisplayDuration = 5000; // How long each tip is displayed (5 seconds)
202912
+ #imageDisplayDuration = 2000; // How long each image within a tip is displayed (2 seconds)
202913
+
202914
+ // DOM element references (cached for performance)
202915
+ tipElement = null;
202916
+ loadingAnimation = null;
202917
+ tipImageElement = null;
202918
+ tipImageContainer = null;
202919
+
202903
202920
  constructor() {
202904
-
202905
202921
  super();
202906
202922
  this.attachShadow({ mode: 'open' });
202907
202923
  }
202908
202924
 
202909
202925
  connectedCallback() {
202910
-
202926
+ // Set up the initial structure of the component in its Shadow DOM
202911
202927
  this.shadowRoot.innerHTML = `
202912
- <style>
202913
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideAssi.css";
202914
- ${ninegrid.getCustomPath(this,"ideAssi.css")}
202915
- </style>
202928
+ <style>
202929
+ /* Import ninegrid's base AI CSS */
202930
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ai.css";
202931
+ /* Import custom CSS specific to this component (adjust path as needed) */
202932
+ ${ninegrid.getCustomPath(this, "ai.css")}
202916
202933
 
202917
- <nx-dialog>
202918
- <div class="buttons">
202919
- adfa
202920
- </div>
202921
-
202922
- </nx-dialog>
202923
- `;
202934
+ /* Component-specific styles */
202935
+ :host {
202936
+ display: flex;
202937
+ justify-content: flex-start;
202938
+ padding: 5px;
202939
+ flex-direction: column;
202940
+ }
202941
+
202942
+ .chat-message.loading-tips-message {
202943
+ max-width: 80%;
202944
+ border-radius: 8px;
202945
+ font-size: 14px;
202946
+ background-color: #fff;
202947
+ color: black;
202948
+ align-self: flex-start;
202949
+ text-align: left;
202950
+ position: relative;
202951
+ box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.15);
202952
+ padding: 12px 16px;
202953
+ display: flex;
202954
+ flex-direction: column;
202955
+ align-items: flex-start;
202956
+ padding-bottom: 8px; /* Adjusted for image container */
202957
+ }
202958
+
202959
+ .loading-animation {
202960
+ width: 20px;
202961
+ height: 20px;
202962
+ border: 3px solid #f3f3f3;
202963
+ border-top: 3px solid #3498db;
202964
+ border-radius: 50%;
202965
+ animation: spin 1s linear infinite;
202966
+ margin-bottom: 8px;
202967
+ display: none; /* Controlled by JS */
202968
+ }
202969
+
202970
+ .tip-content {
202971
+ width: 100%;
202972
+ }
202973
+
202974
+ .tip-label {
202975
+ font-size: 0.85em;
202976
+ color: #666;
202977
+ margin: 0 0 5px 0;
202978
+ font-weight: bold;
202979
+ }
202980
+
202981
+ .current-tip {
202982
+ font-size: 1em;
202983
+ margin: 0;
202984
+ color: #333;
202985
+ line-height: 1.5;
202986
+ margin-bottom: 10px; /* Space between text and image */
202987
+ }
202988
+
202989
+ .tip-image-container {
202990
+ width: 100%;
202991
+ max-height: 150px; /* Max height for the image area */
202992
+ overflow: hidden;
202993
+ display: flex;
202994
+ justify-content: center;
202995
+ align-items: center;
202996
+ background-color: #f0f0f0; /* Optional background for image area */
202997
+ border-radius: 4px;
202998
+ margin-top: 5px;
202999
+ display: none; /* Controlled by JS */
203000
+ }
203001
+
203002
+ .tip-image {
203003
+ max-width: 100%;
203004
+ max-height: 100%;
203005
+ object-fit: contain; /* Ensures image fits without cropping, maintaining aspect ratio */
203006
+ border-radius: 4px;
203007
+ display: none; /* Controlled by JS */
203008
+ }
203009
+
203010
+ @keyframes spin {
203011
+ 0% { transform: rotate(0deg); }
203012
+ 100% { transform: rotate(360deg); }
203013
+ }
203014
+ </style>
203015
+ <div class="chat-message loading-tips-message">
203016
+ <div class="loading-animation"></div>
203017
+ <div class="tip-content">
203018
+ <p class="tip-label">오늘의 AI 팁:</p>
203019
+ <p class="current-tip"></p>
203020
+ <div class="tip-image-container">
203021
+ <img class="tip-image" src="" alt="팁 관련 이미지">
203022
+ </div>
203023
+ </div>
203024
+ </div>
203025
+ `;
203026
+ // Cache DOM element references
203027
+ this.tipElement = this.shadowRoot.querySelector('.current-tip');
203028
+ this.loadingAnimation = this.shadowRoot.querySelector('.loading-animation');
203029
+ this.tipImageElement = this.shadowRoot.querySelector('.tip-image');
203030
+ this.tipImageContainer = this.shadowRoot.querySelector('.tip-image-container');
203031
+ }
203032
+
203033
+ disconnectedCallback() {
203034
+ // Clean up all intervals when the component is removed from the DOM
203035
+ this.stopTips();
203036
+ }
203037
+
203038
+ /**
203039
+ * Loads tips asynchronously from a JSON URL and starts displaying them.
203040
+ * @param {string} tipsUrl - The URL of the JSON file containing tip data.
203041
+ * @param {string} tipCategory - The key in the JSON file's data that corresponds to the desired tip array (e.g., 'componentTips').
203042
+ */
203043
+ async startTips(tipsUrl, tipCategory) {
203044
+ if (!tipsUrl || !tipCategory) {
203045
+ console.error("Tips URL or category not provided for aiLoadingTips.");
203046
+ return;
203047
+ }
203048
+
203049
+ try {
203050
+ const response = await fetch(tipsUrl);
203051
+ if (!response.ok) {
203052
+ throw new Error(`Failed to fetch tips: ${response.statusText}`);
203053
+ }
203054
+ const data = await response.json();
203055
+ const tips = data[tipCategory];
203056
+
203057
+ if (!tips || tips.length === 0) {
203058
+ console.warn(`No tips found for category '${tipCategory}' in ${tipsUrl}.`);
203059
+ return;
203060
+ }
203061
+
203062
+ this.#tips = tips;
203063
+ this.#currentTipIndex = 0;
203064
+ this.stopTips(); // Stop any existing intervals before starting new ones
203065
+
203066
+ this.showCurrentTip(); // Display the first tip immediately
203067
+
203068
+ // Start interval for cycling through tips
203069
+ this.#tipIntervalId = setInterval(() => {
203070
+ this.showNextTip();
203071
+ }, this.#tipDisplayDuration);
203072
+
203073
+ this.loadingAnimation.style.display = 'block'; // Show the loading spinner
203074
+ } catch (error) {
203075
+ console.error("Error loading tips:", error);
203076
+ this.stopTips(); // Stop intervals and hide animations on error
203077
+ if (this.tipElement) {
203078
+ this.tipElement.textContent = "팁을 불러오는 중 오류가 발생했습니다.";
203079
+ }
203080
+ }
203081
+ }
203082
+
203083
+ /**
203084
+ * Stops all tip and image display intervals and hides related UI elements.
203085
+ */
203086
+ stopTips() {
203087
+ if (this.#tipIntervalId) {
203088
+ clearInterval(this.#tipIntervalId);
203089
+ this.#tipIntervalId = null;
203090
+ }
203091
+ if (this.#imageIntervalId) {
203092
+ clearInterval(this.#imageIntervalId);
203093
+ this.#imageIntervalId = null;
203094
+ }
203095
+ this.loadingAnimation.style.display = 'none';
203096
+ this.tipImageElement.style.display = 'none'; // Hide image
203097
+ this.tipImageElement.src = ''; // Clear image source
203098
+ this.tipImageContainer.style.display = 'none'; // Hide image container
203099
+ }
203100
+
203101
+ /**
203102
+ * Advances to the next tip and calls showCurrentTip() to display it.
203103
+ */
203104
+ showNextTip() {
203105
+ this.#currentTipIndex = (this.#currentTipIndex + 1) % this.#tips.length;
203106
+ this.showCurrentTip();
203107
+ }
203108
+
203109
+ /**
203110
+ * Displays the current tip's text and manages its associated images.
203111
+ */
203112
+ showCurrentTip() {
203113
+ const currentTip = this.#tips[this.#currentTipIndex];
203114
+ if (this.tipElement && currentTip) {
203115
+ this.tipElement.textContent = currentTip.text;
203116
+
203117
+ // Clear any existing image cycling interval for the previous tip
203118
+ if (this.#imageIntervalId) {
203119
+ clearInterval(this.#imageIntervalId);
203120
+ this.#imageIntervalId = null;
203121
+ }
203122
+
203123
+ // Image handling logic
203124
+ if (currentTip.images && currentTip.images.length > 0) {
203125
+ this.#currentImageIndex = 0; // Reset image index for the new tip
203126
+ this.showCurrentImage(currentTip.images[this.#currentImageIndex]);
203127
+ this.tipImageElement.style.display = 'block'; // Show the image
203128
+ this.tipImageContainer.style.display = 'flex'; // Show the image container
203129
+
203130
+ if (currentTip.images.length > 1) {
203131
+ // If there's more than one image, start cycling through them
203132
+ this.#imageIntervalId = setInterval(() => {
203133
+ this.#currentImageIndex = (this.#currentImageIndex + 1) % currentTip.images.length;
203134
+ this.showCurrentImage(currentTip.images[this.#currentImageIndex]);
203135
+ }, this.#imageDisplayDuration);
203136
+ }
203137
+ } else {
203138
+ // If no images are provided for this tip, hide the image elements
203139
+ this.tipImageElement.style.display = 'none';
203140
+ this.tipImageContainer.style.display = 'none';
203141
+ this.tipImageElement.src = ''; // Clear image source
203142
+ }
203143
+ }
203144
+ }
203145
+
203146
+ /**
203147
+ * Sets the source of the image element to display the given image.
203148
+ * @param {string} imageUrl - The URL of the image to display.
203149
+ */
203150
+ showCurrentImage(imageUrl) {
203151
+ if (this.tipImageElement) {
203152
+ this.tipImageElement.src = imageUrl;
203153
+ }
203154
+ }
203155
+ }
203156
+
203157
+ // Define the custom element
203158
+ customElements.define("ide-loading-tips", IdeLoadingTips);
203159
+
203160
+ class IdeTipPopup extends HTMLElement {
203161
+ #ideLoadingTipsInstance = null; // aiLoadingTips 인스턴스를 저장할 변수
203162
+ #tipsUrl = '/data/tips.json'; // 팁 JSON 파일의 기본 경로 (public 폴더 내)
203163
+ #tipCategory = 'componentTips'; // 기본으로 표시할 팁 카테고리
203164
+
203165
+ constructor() {
203166
+ super();
203167
+ this.attachShadow({ mode: 'open' });
203168
+ }
203169
+
203170
+ connectedCallback() {
203171
+ this.shadowRoot.innerHTML = `
203172
+ <style>
203173
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideAssi.css";
203174
+ ${ninegrid.getCustomPath(this,"ideAssi.css")}
203175
+
203176
+ /* ide-tip-popup 전용 스타일 (필요시 추가) */
203177
+ .dialog-header {
203178
+ font-size: 1.2em;
203179
+ font-weight: bold;
203180
+ margin-bottom: 15px;
203181
+ text-align: center;
203182
+ color: #333;
203183
+ padding-top: 10px; /* 헤더 위 공간 */
203184
+ }
203185
+ .dialog-content {
203186
+ /* aiLoadingTips가 들어갈 컨테이너 */
203187
+ padding: 0 5px;
203188
+ }
203189
+ /* nx-dialog의 닫기 버튼과 겹치지 않도록 조정 */
203190
+ nx-dialog::part(close-button) { /* nx-dialog의 내부 닫기 버튼에 접근 */
203191
+ top: 10px;
203192
+ right: 10px;
203193
+ }
203194
+ </style>
203195
+
203196
+ <nx-dialog>
203197
+ <div slot="header" class="dialog-header">AI가 답변을 준비 중입니다...</div>
203198
+ <div class="dialog-content"></div>
203199
+ <div slot="footer" class="dialog-footer"></div>
203200
+ </nx-dialog>
203201
+ `;
202924
203202
 
202925
203203
  this.#init();
202926
203204
  }
202927
203205
 
202928
203206
  #init = () => {
202929
-
203207
+ // nx-dialog가 DOM에 추가된 후 내부 요소에 접근
203208
+ const dialog = this.shadowRoot.querySelector('nx-dialog');
203209
+ if (dialog) {
203210
+ // dialog가 닫힐 때 aiLoadingTips의 애니메이션 중지
203211
+ dialog.addEventListener('close', () => {
203212
+ if (this.#aiLoadingTipsInstance) {
203213
+ this.#aiLoadingTipsInstance.stopTips();
203214
+ // 팝업이 닫힐 때 aiLoadingTips 인스턴스 제거 (선택 사항)
203215
+ this.#aiLoadingTipsInstance.remove();
203216
+ this.#aiLoadingTipsInstance = null;
203217
+ }
203218
+ });
203219
+ }
202930
203220
  }
202931
203221
 
202932
- popup = () => {
203222
+ /**
203223
+ * 팁 팝업을 표시하고 aiLoadingTips를 시작합니다.
203224
+ * @param {string} [category='componentTips'] - 표시할 팁의 카테고리 ('componentTips' 또는 'generalAITips')
203225
+ * @param {string} [tipsUrl='/data/tips.json'] - 팁 JSON 파일의 URL
203226
+ */
203227
+ async popup(category = 'componentTips', tipsUrl = '/tips/tip.json') {
203228
+ this.#tipCategory = category;
203229
+ this.#tipsUrl = tipsUrl;
203230
+
203231
+ const dialogContent = this.shadowRoot.querySelector('.dialog-content');
203232
+ if (!dialogContent) {
203233
+ console.error("Popup content container not found.");
203234
+ return;
203235
+ }
203236
+
203237
+ // 기존 aiLoadingTips 인스턴스가 있다면 제거
203238
+ if (this.#ideLoadingTipsInstance) {
203239
+ this.#ideLoadingTipsInstance.stopTips();
203240
+ this.#ideLoadingTipsInstance.remove();
203241
+ this.#ideLoadingTipsInstance = null;
203242
+ }
203243
+
203244
+ // 새로운 aiLoadingTips 인스턴스 생성 및 추가
203245
+ this.#ideLoadingTipsInstance = document.createElement('ide-loading-tips');
203246
+ dialogContent.appendChild(this.#ideLoadingTipsInstance);
203247
+
203248
+ // 팁 로딩 시작
203249
+ await this.#ideLoadingTipsInstance.startTips(this.#tipsUrl, this.#tipCategory);
203250
+
203251
+ // nx-dialog 팝업 표시
202933
203252
  this.shadowRoot.querySelector('nx-dialog')?.showModal();
202934
203253
  };
202935
203254
 
203255
+ /**
203256
+ * 팁 팝업을 닫습니다.
203257
+ */
202936
203258
  close = () => {
202937
203259
  this.shadowRoot.querySelector('nx-dialog')?.close();
203260
+ // close 이벤트 리스너에서 aiLoadingTips 정리 로직이 실행될 것임
202938
203261
  };
202939
203262
  }
202940
203263