diff --git a/package.json b/package.json index 9188751..a4b6cf0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lms", - "version": "1.0.6", + "version": "1.0.7", "description": "Autonomy Studio LMS Electron app", "main": "./out/main/index.js", "author": { diff --git a/src/main/index.ts b/src/main/index.ts index 12a289b..0ef52bc 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -3185,6 +3185,22 @@ function createSplashWindow(): void { }) } +function updateSplashStatus(message: string): void { + if (!splashWindow || splashWindow.isDestroyed()) return + const safeMessage = JSON.stringify(message) + splashWindow.webContents.executeJavaScript( + `window.setSplashStatus && window.setSplashStatus(${safeMessage})` + ).catch(() => undefined) +} + +function closeSplashAndShowMain(): void { + if (mainWindow && !mainWindow.isDestroyed() && !mainWindow.isVisible()) { + splashWindow?.close() + splashWindow = null + mainWindow.show() + } +} + function createWindow(): void { const { width, height } = screen.getPrimaryDisplay().workAreaSize @@ -3210,10 +3226,9 @@ function createWindow(): void { suppressSystemContextMenu(mainWindow) mainWindow.once('ready-to-show', () => { setTimeout(() => { - splashWindow?.close() - splashWindow = null - mainWindow?.show() + updateSplashStatus('로그인 세션 확인 중...') }, 1100) + setTimeout(closeSplashAndShowMain, 8000) }) mainWindow.on('closed', () => { mainWindow = null @@ -3227,6 +3242,12 @@ function createWindow(): void { } function registerLocalWorkspaceHandlers(): void { + ipcMain.on('lms-boot-status', (_event, message: string) => { + updateSplashStatus(message) + }) + ipcMain.on('lms-boot-done', () => { + closeSplashAndShowMain() + }) ipcMain.handle('local-workspace:modules:list', (_event, workspaceRoot?: string | null, courseModules?: CourseModuleInput[] | null) => collectAvailableModules(workspaceRoot, courseModules)) ipcMain.handle('local-workspace:modules:install', (_event, moduleName: string, workspaceRoot?: string | null, folderName?: string | null, templateRepositoryUrl?: string | null, studentRepositoryUrl?: string | null, options?: ModuleInstallOptions | null) => installModuleFromCache(moduleName, workspaceRoot, folderName, templateRepositoryUrl, studentRepositoryUrl, options)) ipcMain.handle('local-workspace:modules:delete', (_event, moduleName: string, workspaceRoot?: string | null, folderName?: string | null) => deleteWorkspaceModule(moduleName, workspaceRoot, folderName)) diff --git a/src/preload/index.ts b/src/preload/index.ts index 746d687..aae4c6f 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -84,6 +84,10 @@ console.info('[lms-preload] launcher auth resolved', { contextBridge.exposeInMainWorld('electron', electronAPI) contextBridge.exposeInMainWorld('launcherAuth', launcherAuth) +contextBridge.exposeInMainWorld('lmsBootSplash', { + status: (message: string) => ipcRenderer.send('lms-boot-status', message), + done: () => ipcRenderer.send('lms-boot-done') +}) contextBridge.exposeInMainWorld('titlebar', { minimize: () => ipcRenderer.send('titlebar-minimize'), maximize: () => ipcRenderer.send('titlebar-maximize'), diff --git a/src/renderer/public/splash.html b/src/renderer/public/splash.html index 6ede4b5..51a5896 100644 --- a/src/renderer/public/splash.html +++ b/src/renderer/public/splash.html @@ -77,14 +77,12 @@ diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 01d284a..956edb0 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -2662,6 +2662,18 @@ function LocalPcWorkspaceMockup({ ) } +function BootLoadingOverlay({ message }: { message: string }): React.JSX.Element { + return ( +
+
+
+

Autonomy Studio LMS

+

{message}

+
+
+ ) +} + export default function App(): React.JSX.Element { const restoredStudioSessionRef = useRef(readStudioRuntimeSessionState()) const previousWeekStateRef = useRef>({}) @@ -2683,6 +2695,8 @@ export default function App(): React.JSX.Element { const [authSession, setAuthSession] = useState(null) const authSessionRef = useRef(null) const [authReady, setAuthReady] = useState(false) + const [studentCoursesLoading, setStudentCoursesLoading] = useState(false) + const [curriculumLoading, setCurriculumLoading] = useState(false) const [sidebarCollapsed, setSidebarCollapsed] = useState(false) const [userSidebarCollapsed, setUserSidebarCollapsed] = useState(false) const [platformModalOpen, setPlatformModalOpen] = useState(false) @@ -2939,6 +2953,7 @@ export default function App(): React.JSX.Element { const loadStudentCourses = useCallback(async (options: { autoSelectSingle?: boolean } = {}): Promise => { if (!authReady || !authSession?.accessToken) return + setStudentCoursesLoading(true) try { const courses = await fetchStudentCourses(fetchWithFreshAuth) @@ -2967,6 +2982,8 @@ export default function App(): React.JSX.Element { setStudentCourses([]) setStudentCoursesLoaded(true) setCourseSelectionVisible(true) + } finally { + setStudentCoursesLoading(false) } }, [authReady, authSession?.accessToken, fetchWithFreshAuth]) @@ -2982,6 +2999,7 @@ export default function App(): React.JSX.Element { const loadCurriculum = useCallback(async (): Promise => { if (!authReady || !studentCoursesLoaded || courseSelectionVisible || !selectedCourseId) return + setCurriculumLoading(true) try { const payload = await fetchCurriculum(fetchWithFreshAuth, selectedCourseId) const nextWeeks = payload?.weeks ?? [] @@ -3023,6 +3041,8 @@ export default function App(): React.JSX.Element { setLearningStats([]) setPlatformOptions([]) setSelectedWeekState(null) + } finally { + setCurriculumLoading(false) } }, [authReady, authSession?.accessToken, courseSelectionVisible, fetchWithFreshAuth, selectedCourseId, selectedWeekState?.week, studentCoursesLoaded]) @@ -3924,6 +3944,22 @@ export default function App(): React.JSX.Element { setMaterialDownloadingResourceIds((current) => current.filter((id) => id !== resource.id)) } } + const bootLoadingMessage = !authReady + ? '로그인 세션 확인 중...' + : authSession?.accessToken && (!studentCoursesLoaded || studentCoursesLoading) + ? '강좌 정보 불러오는 중...' + : authSession?.accessToken && curriculumLoading + ? '커리큘럼 및 플랫폼 정보 가져오는 중...' + : '' + const bootLoadingVisible = Boolean(bootLoadingMessage) + useEffect(() => { + if (bootLoadingMessage) { + window.lmsBootSplash?.status(bootLoadingMessage) + return + } + + window.lmsBootSplash?.done() + }, [bootLoadingMessage]) return (
@@ -4219,6 +4255,7 @@ export default function App(): React.JSX.Element {
+ {bootLoadingVisible && } ) } diff --git a/src/renderer/src/env.d.ts b/src/renderer/src/env.d.ts index e264632..985e1b8 100644 --- a/src/renderer/src/env.d.ts +++ b/src/renderer/src/env.d.ts @@ -37,6 +37,10 @@ interface Window { handoffToken: string | null audienceApp: string } + lmsBootSplash?: { + status: (message: string) => void + done: () => void + } titlebar: { minimize: () => void maximize: () => void