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.
- package/dist/bundle.cjs.js +340 -17
- package/dist/bundle.esm.js +340 -17
- package/dist/components/ideLoadingTips.js +259 -0
- package/dist/components/ideTipPopup.js +84 -19
- package/package.json +1 -1
- package/src/components/ideLoadingTips.js +259 -0
- package/src/components/ideTipPopup.js +84 -19
package/dist/bundle.cjs.js
CHANGED
|
@@ -202898,43 +202898,366 @@ class ideAssiSettings extends HTMLElement
|
|
|
202898
202898
|
|
|
202899
202899
|
customElements.define("ide-assi-settings", ideAssiSettings);
|
|
202900
202900
|
|
|
202901
|
-
|
|
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
|
-
|
|
202913
|
-
|
|
202914
|
-
|
|
202915
|
-
|
|
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
|
-
|
|
202918
|
-
|
|
202919
|
-
|
|
202920
|
-
|
|
202921
|
-
|
|
202922
|
-
|
|
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
|
-
|
|
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
|
|