update lms app runtime integration

This commit is contained in:
2026-07-06 16:41:48 +09:00
parent 1c2d5f24ef
commit 9a1eec48e9
6 changed files with 75 additions and 11 deletions
+1 -1
View File
@@ -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": {
+24 -3
View File
@@ -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))
+4
View File
@@ -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'),
+5 -7
View File
@@ -77,14 +77,12 @@
<script>
document.getElementById('splash-image').src = './assets/splash.png'
const texts = ['학습 환경 준비 중...', '커리큘럼 불러오는 중...']
const el = document.getElementById('loading-text')
let index = 0
setInterval(() => {
index = (index + 1) % texts.length
el.textContent = texts[index]
}, 1200)
window.setSplashStatus = (message) => {
if (typeof message === 'string' && message.trim()) {
el.textContent = message
}
}
</script>
</body>
</html>
+37
View File
@@ -2662,6 +2662,18 @@ function LocalPcWorkspaceMockup({
)
}
function BootLoadingOverlay({ message }: { message: string }): React.JSX.Element {
return (
<div className="fixed inset-0 z-[200] grid place-items-center bg-black/24 backdrop-blur-[3px]">
<section className="w-[324px] rounded-xl border border-white/14 bg-[rgba(18,24,36,0.78)] px-6 py-5 text-center shadow-[0_24px_80px_rgba(0,0,0,0.42),inset_0_1px_0_rgba(255,255,255,0.10)] backdrop-blur-2xl">
<div className="mx-auto mb-4 h-9 w-9 animate-spin rounded-full border-2 border-white/12 border-t-[#4f8dff]" />
<p className="text-[14px] font-bold text-white">Autonomy Studio LMS</p>
<p className="mt-2 text-[12px] font-semibold text-white/58">{message}</p>
</section>
</div>
)
}
export default function App(): React.JSX.Element {
const restoredStudioSessionRef = useRef<StudioRuntimeSessionState | null>(readStudioRuntimeSessionState())
const previousWeekStateRef = useRef<Record<number, CurriculumWeek['state']>>({})
@@ -2683,6 +2695,8 @@ export default function App(): React.JSX.Element {
const [authSession, setAuthSession] = useState<LmsAuthSession | null>(null)
const authSessionRef = useRef<LmsAuthSession | null>(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<void> => {
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<void> => {
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 (
<div className="h-screen w-screen overflow-hidden bg-[#0a0b0e]">
@@ -4219,6 +4255,7 @@ export default function App(): React.JSX.Element {
</main>
</div>
</div>
{bootLoadingVisible && <BootLoadingOverlay message={bootLoadingMessage} />}
</div>
)
}
+4
View File
@@ -37,6 +37,10 @@ interface Window {
handoffToken: string | null
audienceApp: string
}
lmsBootSplash?: {
status: (message: string) => void
done: () => void
}
titlebar: {
minimize: () => void
maximize: () => void