update lms app runtime integration
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lms",
|
"name": "lms",
|
||||||
"version": "1.0.6",
|
"version": "1.0.7",
|
||||||
"description": "Autonomy Studio LMS Electron app",
|
"description": "Autonomy Studio LMS Electron app",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
+24
-3
@@ -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 {
|
function createWindow(): void {
|
||||||
const { width, height } = screen.getPrimaryDisplay().workAreaSize
|
const { width, height } = screen.getPrimaryDisplay().workAreaSize
|
||||||
|
|
||||||
@@ -3210,10 +3226,9 @@ function createWindow(): void {
|
|||||||
suppressSystemContextMenu(mainWindow)
|
suppressSystemContextMenu(mainWindow)
|
||||||
mainWindow.once('ready-to-show', () => {
|
mainWindow.once('ready-to-show', () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
splashWindow?.close()
|
updateSplashStatus('로그인 세션 확인 중...')
|
||||||
splashWindow = null
|
|
||||||
mainWindow?.show()
|
|
||||||
}, 1100)
|
}, 1100)
|
||||||
|
setTimeout(closeSplashAndShowMain, 8000)
|
||||||
})
|
})
|
||||||
mainWindow.on('closed', () => {
|
mainWindow.on('closed', () => {
|
||||||
mainWindow = null
|
mainWindow = null
|
||||||
@@ -3227,6 +3242,12 @@ function createWindow(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function registerLocalWorkspaceHandlers(): 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: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: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))
|
ipcMain.handle('local-workspace:modules:delete', (_event, moduleName: string, workspaceRoot?: string | null, folderName?: string | null) => deleteWorkspaceModule(moduleName, workspaceRoot, folderName))
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ console.info('[lms-preload] launcher auth resolved', {
|
|||||||
|
|
||||||
contextBridge.exposeInMainWorld('electron', electronAPI)
|
contextBridge.exposeInMainWorld('electron', electronAPI)
|
||||||
contextBridge.exposeInMainWorld('launcherAuth', launcherAuth)
|
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', {
|
contextBridge.exposeInMainWorld('titlebar', {
|
||||||
minimize: () => ipcRenderer.send('titlebar-minimize'),
|
minimize: () => ipcRenderer.send('titlebar-minimize'),
|
||||||
maximize: () => ipcRenderer.send('titlebar-maximize'),
|
maximize: () => ipcRenderer.send('titlebar-maximize'),
|
||||||
|
|||||||
@@ -77,14 +77,12 @@
|
|||||||
<script>
|
<script>
|
||||||
document.getElementById('splash-image').src = './assets/splash.png'
|
document.getElementById('splash-image').src = './assets/splash.png'
|
||||||
|
|
||||||
const texts = ['학습 환경 준비 중...', '커리큘럼 불러오는 중...']
|
|
||||||
const el = document.getElementById('loading-text')
|
const el = document.getElementById('loading-text')
|
||||||
let index = 0
|
window.setSplashStatus = (message) => {
|
||||||
|
if (typeof message === 'string' && message.trim()) {
|
||||||
setInterval(() => {
|
el.textContent = message
|
||||||
index = (index + 1) % texts.length
|
}
|
||||||
el.textContent = texts[index]
|
}
|
||||||
}, 1200)
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -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 {
|
export default function App(): React.JSX.Element {
|
||||||
const restoredStudioSessionRef = useRef<StudioRuntimeSessionState | null>(readStudioRuntimeSessionState())
|
const restoredStudioSessionRef = useRef<StudioRuntimeSessionState | null>(readStudioRuntimeSessionState())
|
||||||
const previousWeekStateRef = useRef<Record<number, CurriculumWeek['state']>>({})
|
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 [authSession, setAuthSession] = useState<LmsAuthSession | null>(null)
|
||||||
const authSessionRef = useRef<LmsAuthSession | null>(null)
|
const authSessionRef = useRef<LmsAuthSession | null>(null)
|
||||||
const [authReady, setAuthReady] = useState(false)
|
const [authReady, setAuthReady] = useState(false)
|
||||||
|
const [studentCoursesLoading, setStudentCoursesLoading] = useState(false)
|
||||||
|
const [curriculumLoading, setCurriculumLoading] = useState(false)
|
||||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
|
||||||
const [userSidebarCollapsed, setUserSidebarCollapsed] = useState(false)
|
const [userSidebarCollapsed, setUserSidebarCollapsed] = useState(false)
|
||||||
const [platformModalOpen, setPlatformModalOpen] = 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> => {
|
const loadStudentCourses = useCallback(async (options: { autoSelectSingle?: boolean } = {}): Promise<void> => {
|
||||||
if (!authReady || !authSession?.accessToken) return
|
if (!authReady || !authSession?.accessToken) return
|
||||||
|
|
||||||
|
setStudentCoursesLoading(true)
|
||||||
try {
|
try {
|
||||||
const courses = await fetchStudentCourses(fetchWithFreshAuth)
|
const courses = await fetchStudentCourses(fetchWithFreshAuth)
|
||||||
|
|
||||||
@@ -2967,6 +2982,8 @@ export default function App(): React.JSX.Element {
|
|||||||
setStudentCourses([])
|
setStudentCourses([])
|
||||||
setStudentCoursesLoaded(true)
|
setStudentCoursesLoaded(true)
|
||||||
setCourseSelectionVisible(true)
|
setCourseSelectionVisible(true)
|
||||||
|
} finally {
|
||||||
|
setStudentCoursesLoading(false)
|
||||||
}
|
}
|
||||||
}, [authReady, authSession?.accessToken, fetchWithFreshAuth])
|
}, [authReady, authSession?.accessToken, fetchWithFreshAuth])
|
||||||
|
|
||||||
@@ -2982,6 +2999,7 @@ export default function App(): React.JSX.Element {
|
|||||||
const loadCurriculum = useCallback(async (): Promise<void> => {
|
const loadCurriculum = useCallback(async (): Promise<void> => {
|
||||||
if (!authReady || !studentCoursesLoaded || courseSelectionVisible || !selectedCourseId) return
|
if (!authReady || !studentCoursesLoaded || courseSelectionVisible || !selectedCourseId) return
|
||||||
|
|
||||||
|
setCurriculumLoading(true)
|
||||||
try {
|
try {
|
||||||
const payload = await fetchCurriculum(fetchWithFreshAuth, selectedCourseId)
|
const payload = await fetchCurriculum(fetchWithFreshAuth, selectedCourseId)
|
||||||
const nextWeeks = payload?.weeks ?? []
|
const nextWeeks = payload?.weeks ?? []
|
||||||
@@ -3023,6 +3041,8 @@ export default function App(): React.JSX.Element {
|
|||||||
setLearningStats([])
|
setLearningStats([])
|
||||||
setPlatformOptions([])
|
setPlatformOptions([])
|
||||||
setSelectedWeekState(null)
|
setSelectedWeekState(null)
|
||||||
|
} finally {
|
||||||
|
setCurriculumLoading(false)
|
||||||
}
|
}
|
||||||
}, [authReady, authSession?.accessToken, courseSelectionVisible, fetchWithFreshAuth, selectedCourseId, selectedWeekState?.week, studentCoursesLoaded])
|
}, [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))
|
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 (
|
return (
|
||||||
<div className="h-screen w-screen overflow-hidden bg-[#0a0b0e]">
|
<div className="h-screen w-screen overflow-hidden bg-[#0a0b0e]">
|
||||||
@@ -4219,6 +4255,7 @@ export default function App(): React.JSX.Element {
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{bootLoadingVisible && <BootLoadingOverlay message={bootLoadingMessage} />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+4
@@ -37,6 +37,10 @@ interface Window {
|
|||||||
handoffToken: string | null
|
handoffToken: string | null
|
||||||
audienceApp: string
|
audienceApp: string
|
||||||
}
|
}
|
||||||
|
lmsBootSplash?: {
|
||||||
|
status: (message: string) => void
|
||||||
|
done: () => void
|
||||||
|
}
|
||||||
titlebar: {
|
titlebar: {
|
||||||
minimize: () => void
|
minimize: () => void
|
||||||
maximize: () => void
|
maximize: () => void
|
||||||
|
|||||||
Reference in New Issue
Block a user