Skip to content

Commit eb5eff0

Browse files
authored
Merge pull request #8 from tcf775/feature/mobile
update
2 parents cf5c10d + 2275963 commit eb5eff0

7 files changed

Lines changed: 1136 additions & 44 deletions

File tree

js/course-manager.js

Lines changed: 69 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -69,45 +69,85 @@ class CourseManager {
6969
}
7070

7171
/**
72-
* 各コースのチャレンジデータを読み込み
72+
* 各コースのチャレンジデータを読み込み(遅延読み込み対応)
7373
*/
7474
async loadChallengeData() {
75+
// 遅延読み込み: 現在選択されているコースのみ読み込み
76+
const selectedCourseId = this.progressManager?.getSelectedCourse();
77+
7578
const challengeFiles = {
7679
'sql-basics': 'slides/challenges.json',
7780
'db-fundamentals': 'slides/db-fundamentals-challenges.json',
7881
'big-data-basics': 'slides/big-data-basics-challenges.json'
7982
};
8083

81-
for (const [courseId, filePath] of Object.entries(challengeFiles)) {
82-
try {
83-
const response = await fetch(filePath);
84-
if (response.ok) {
85-
this.challengeData[courseId] = await response.json();
86-
} else {
87-
console.warn(`チャレンジファイルが見つかりません: ${filePath}`);
88-
this.challengeData[courseId] = [];
89-
}
90-
} catch (error) {
91-
console.error(`チャレンジデータ読み込みエラー (${courseId}):`, error);
84+
// 選択されたコースがある場合は、そのコースのみ読み込み
85+
if (selectedCourseId && challengeFiles[selectedCourseId]) {
86+
await this.loadCourseChallenge(selectedCourseId, challengeFiles[selectedCourseId]);
87+
} else {
88+
// 初回アクセス時は基本コースのみ読み込み
89+
await this.loadCourseChallenge('sql-basics', challengeFiles['sql-basics']);
90+
}
91+
}
92+
93+
/**
94+
* 特定コースのチャレンジデータを読み込み
95+
*/
96+
async loadCourseChallenge(courseId, filePath) {
97+
// 既に読み込み済みの場合はスキップ
98+
if (this.challengeData[courseId]) {
99+
return this.challengeData[courseId];
100+
}
101+
102+
try {
103+
const response = await fetch(filePath);
104+
if (response.ok) {
105+
this.challengeData[courseId] = await response.json();
106+
console.log(`チャレンジデータを読み込みました: ${courseId}`);
107+
} else {
108+
console.warn(`チャレンジファイルが見つかりません: ${filePath}`);
109+
this.challengeData[courseId] = [];
110+
}
111+
} catch (error) {
112+
console.error(`チャレンジデータ読み込みエラー (${courseId}):`, error);
113+
114+
// ErrorHandlerを使用してチャレンジデータエラーを処理
115+
if (window.errorHandler) {
116+
const result = await window.errorHandler.handleError('CHALLENGE_LOAD_ERROR', error, {
117+
courseId: courseId,
118+
challengeFile: filePath,
119+
operation: 'loadCourseChallenge'
120+
});
92121

93-
// ErrorHandlerを使用してチャレンジデータエラーを処理
94-
if (window.errorHandler) {
95-
const result = await window.errorHandler.handleError('CHALLENGE_LOAD_ERROR', error, {
96-
courseId: courseId,
97-
challengeFile: filePath,
98-
operation: 'loadChallengeData'
99-
});
100-
101-
if (result.success) {
102-
this.challengeData[courseId] = result.data;
103-
} else {
104-
this.challengeData[courseId] = [];
105-
}
122+
if (result.success) {
123+
this.challengeData[courseId] = result.data;
106124
} else {
107125
this.challengeData[courseId] = [];
108126
}
127+
} else {
128+
this.challengeData[courseId] = [];
109129
}
110130
}
131+
132+
return this.challengeData[courseId];
133+
}
134+
135+
/**
136+
* 必要に応じてコースのチャレンジデータを遅延読み込み
137+
*/
138+
async ensureCourseDataLoaded(courseId) {
139+
if (!this.challengeData[courseId]) {
140+
const challengeFiles = {
141+
'sql-basics': 'slides/challenges.json',
142+
'db-fundamentals': 'slides/db-fundamentals-challenges.json',
143+
'big-data-basics': 'slides/big-data-basics-challenges.json'
144+
};
145+
146+
if (challengeFiles[courseId]) {
147+
await this.loadCourseChallenge(courseId, challengeFiles[courseId]);
148+
}
149+
}
150+
return this.challengeData[courseId] || [];
111151
}
112152

113153
/**
@@ -169,12 +209,15 @@ class CourseManager {
169209
/**
170210
* コースを選択
171211
*/
172-
selectCourse(courseId) {
212+
async selectCourse(courseId) {
173213
const course = this.getCourse(courseId);
174214
if (!course) {
175215
throw new Error(`コースが見つかりません: ${courseId}`);
176216
}
177217

218+
// コースのチャレンジデータを遅延読み込み
219+
await this.ensureCourseDataLoaded(courseId);
220+
178221
this.currentCourse = course;
179222
this.progressManager.saveSelectedCourse(courseId);
180223

js/course-ui.js

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,28 @@ export class CourseUI {
1111
courseList: document.getElementById('course-list'),
1212
appLayout: document.querySelector('.app-layout')
1313
};
14+
15+
// UIOptimizerを初期化
16+
this.uiOptimizer = null;
17+
this.initializeUIOptimizer();
18+
1419
this.bindEvents();
1520
}
1621

22+
/**
23+
* UIOptimizerを初期化
24+
*/
25+
async initializeUIOptimizer() {
26+
try {
27+
const { UIOptimizer } = await import('./ui-optimizer.js');
28+
this.uiOptimizer = new UIOptimizer();
29+
this.uiOptimizer.initialize();
30+
console.log('CourseUI: UIOptimizerが初期化されました');
31+
} catch (error) {
32+
console.warn('UIOptimizerの読み込みに失敗しました:', error);
33+
}
34+
}
35+
1736
/**
1837
* イベントリスナーを設定
1938
*/
@@ -47,19 +66,28 @@ export class CourseUI {
4766
}
4867

4968
/**
50-
* コース一覧を描画
69+
* コース一覧を描画(最適化版)
5170
*/
5271
renderCourseList(courses) {
5372
if (!courses || courses.length === 0) {
5473
this.elements.courseList.innerHTML = '<p>利用可能なコースがありません</p>';
5574
return;
5675
}
5776

58-
const courseCards = courses.map(course => this.createCourseCard(course)).join('');
59-
this.elements.courseList.innerHTML = courseCards;
60-
61-
// コース選択ボタンのイベントリスナーを設定
62-
this.bindCourseSelectionEvents();
77+
// UIOptimizerが利用可能な場合は最適化された更新を使用
78+
if (this.uiOptimizer) {
79+
this.uiOptimizer.updateCourseSelection(courses, 'high');
80+
81+
// イベントリスナーを遅延設定
82+
setTimeout(() => {
83+
this.bindCourseSelectionEvents();
84+
}, 50);
85+
} else {
86+
// フォールバック: 従来の方法
87+
const courseCards = courses.map(course => this.createCourseCard(course)).join('');
88+
this.elements.courseList.innerHTML = courseCards;
89+
this.bindCourseSelectionEvents();
90+
}
6391
}
6492

6593
/**
@@ -185,21 +213,25 @@ export class CourseUI {
185213
}
186214

187215
/**
188-
* コースを選択して学習を開始
216+
* コースを選択して学習を開始(最適化版)
189217
*/
190218
async selectCourse(courseId) {
191219
try {
220+
// ローディング状態を表示
221+
this.showCourseLoadingState(courseId);
222+
192223
// コースを取得
193224
const course = this.courseManager.getCourse(courseId);
194225

195226
// 準備中のコースかチェック
196227
if (course.status === 'coming_soon') {
197228
alert('このコースは現在準備中です。近日公開予定です。');
229+
this.hideCourseLoadingState();
198230
return;
199231
}
200232

201-
// コースを選択
202-
const selectedCourse = this.courseManager.selectCourse(courseId);
233+
// コースを選択(遅延読み込み対応)
234+
const selectedCourse = await this.courseManager.selectCourse(courseId);
203235
console.log(`コースを選択しました: ${selectedCourse.title}`);
204236

205237
// GameEngineにコースを設定
@@ -220,17 +252,52 @@ export class CourseUI {
220252
window.progressUI.onCourseSelected(course);
221253
}
222254

223-
// チャレンジを更新
255+
// チャレンジを更新(非同期で実行)
224256
if (window.uiController && typeof window.uiController.updateChallenge === 'function') {
225-
window.uiController.updateChallenge();
257+
// UI更新を次のフレームで実行
258+
requestAnimationFrame(() => {
259+
window.uiController.updateChallenge();
260+
});
226261
}
227262

263+
this.hideCourseLoadingState();
264+
228265
} catch (error) {
229266
console.error('コース選択エラー:', error);
267+
this.hideCourseLoadingState();
230268
alert(`コース選択に失敗しました: ${error.message}`);
231269
}
232270
}
233271

272+
/**
273+
* コース読み込み状態を表示
274+
*/
275+
showCourseLoadingState(courseId) {
276+
const courseCard = document.querySelector(`[data-course-id="${courseId}"]`);
277+
if (courseCard) {
278+
const selectBtn = courseCard.querySelector('.course-select-btn');
279+
if (selectBtn) {
280+
selectBtn.disabled = true;
281+
selectBtn.textContent = '読み込み中...';
282+
}
283+
}
284+
}
285+
286+
/**
287+
* コース読み込み状態を非表示
288+
*/
289+
hideCourseLoadingState() {
290+
const selectBtns = document.querySelectorAll('.course-select-btn');
291+
selectBtns.forEach(btn => {
292+
btn.disabled = false;
293+
const courseId = btn.dataset.courseId;
294+
const course = this.courseManager.getCourse(courseId);
295+
const progress = this.courseManager.getCourseProgress(courseId);
296+
const isStarted = progress && progress.completedLessons.length > 0;
297+
btn.textContent = isStarted ? '続きから学習' : 'コースを開始';
298+
});
299+
}
300+
234301
/**
235302
* コース詳細情報を表示
236303
*/

js/main.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { ErrorHandler } from './error-handler.js';
1010
import { NotificationSystem } from './notification-system.js';
1111
import { AdaptiveLearning } from './adaptive-learning.js';
1212
import { AdaptiveLearningUI } from './adaptive-learning-ui.js';
13+
import { UIOptimizer } from './ui-optimizer.js';
14+
import { PerformanceMonitor } from './performance-monitor.js';
1315

1416

1517
// グローバル変数
@@ -21,6 +23,8 @@ let courseUI;
2123
let progressUI;
2224
let adaptiveLearning;
2325
let adaptiveLearningUI;
26+
let uiOptimizer;
27+
let performanceMonitor;
2428

2529
// アプリケーション初期化
2630
async function initializeApp() {
@@ -66,11 +70,20 @@ async function initializeApp() {
6670
// 既存のチャレンジ読み込み(フォールバック用)
6771
await gameEngine.loadChallenges();
6872

73+
// UIOptimizer初期化
74+
uiOptimizer = new UIOptimizer();
75+
uiOptimizer.initialize();
76+
77+
// PerformanceMonitor初期化
78+
performanceMonitor = new PerformanceMonitor();
79+
6980
// グローバルアクセス用
7081
window.dbManager = dbManager;
7182
window.gameEngine = gameEngine;
7283
window.courseManager = courseManager;
7384
window.adaptiveLearning = adaptiveLearning;
85+
window.uiOptimizer = uiOptimizer;
86+
window.performanceMonitor = performanceMonitor;
7487

7588
// UIコントローラー初期化
7689
const autoComplete = new SQLAutoComplete();

0 commit comments

Comments
 (0)