UNPKG

codewithgarry

Version:

Girish Sharma's NPX business card - DevOps Engineer & Cloud Architect - Connect with me directly via terminal!

748 lines (636 loc) 22.8 kB
// Enhanced YouTube Player Integration and Monaco Editor Setup // This file contains advanced functionality for the learning platform class YouTubePlayerManager { constructor() { this.player = null; this.currentVideo = null; this.playlist = []; this.playlistIndex = 0; } // Initialize YouTube API initializeYouTubeAPI() { return new Promise((resolve) => { if (window.YT && window.YT.Player) { resolve(); return; } window.onYouTubeIframeAPIReady = resolve; // Load YouTube IFrame API const script = document.createElement('script'); script.src = 'https://www.youtube.com/iframe_api'; document.head.appendChild(script); }); } // Create YouTube player with custom controls async createPlayer(containerId, videoId, options = {}) { await this.initializeYouTubeAPI(); this.player = new YT.Player(containerId, { height: '100%', width: '100%', videoId: videoId, playerVars: { autoplay: options.autoplay || 0, controls: 1, disablekb: 0, enablejsapi: 1, fs: 1, iv_load_policy: 3, modestbranding: 1, playsinline: 1, rel: 0, showinfo: 0 }, events: { onReady: this.onPlayerReady.bind(this), onStateChange: this.onPlayerStateChange.bind(this), onError: this.onPlayerError.bind(this) } }); } onPlayerReady(event) { console.log('YouTube player ready'); // Add custom controls or overlays here this.addCustomControls(); } onPlayerStateChange(event) { if (event.data === YT.PlayerState.ENDED) { // Auto-advance to next item in playlist this.nextPlaylistItem(); } // Track progress for course completion this.trackProgress(); } onPlayerError(event) { console.error('YouTube player error:', event.data); this.handlePlayerError(event.data); } addCustomControls() { const container = this.player.getIframe().parentElement; // Add custom overlay with course-specific controls const overlay = document.createElement('div'); overlay.className = 'youtube-overlay'; overlay.innerHTML = ` <div class="video-overlay-controls"> <button class="overlay-btn" onclick="youTubeManager.takeNotes()"> <i class="fas fa-sticky-note"></i> Take Notes </button> <button class="overlay-btn" onclick="youTubeManager.bookmarkTime()"> <i class="fas fa-bookmark"></i> Bookmark </button> <button class="overlay-btn" onclick="youTubeManager.adjustSpeed()"> <i class="fas fa-tachometer-alt"></i> Speed </button> </div> `; container.appendChild(overlay); } takeNotes() { const currentTime = this.player.getCurrentTime(); const videoTitle = this.currentVideo?.title || 'Unknown Video'; // Open notes panel or modal this.showNotesModal(videoTitle, currentTime); } bookmarkTime() { const currentTime = this.player.getCurrentTime(); const formattedTime = this.formatTime(currentTime); // Save bookmark to local storage or send to backend const bookmark = { videoId: this.currentVideo?.youtubeId, title: this.currentVideo?.title, time: currentTime, formattedTime: formattedTime, timestamp: new Date().toISOString() }; this.saveBookmark(bookmark); this.showNotification(`Bookmarked at ${formattedTime}`, 'success'); } formatTime(seconds) { const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.floor(seconds % 60); return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; } saveBookmark(bookmark) { const bookmarks = JSON.parse(localStorage.getItem('courseBookmarks') || '[]'); bookmarks.push(bookmark); localStorage.setItem('courseBookmarks', JSON.stringify(bookmarks)); } showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.innerHTML = ` <i class="fas fa-${type === 'success' ? 'check' : 'info'}-circle"></i> ${message} `; document.body.appendChild(notification); setTimeout(() => { notification.style.opacity = '0'; setTimeout(() => notification.remove(), 300); }, 3000); } nextPlaylistItem() { if (this.playlistIndex < this.playlist.length - 1) { this.playlistIndex++; this.loadPlaylistItem(this.playlist[this.playlistIndex]); } } previousPlaylistItem() { if (this.playlistIndex > 0) { this.playlistIndex--; this.loadPlaylistItem(this.playlist[this.playlistIndex]); } } trackProgress() { if (!this.player) return; const currentTime = this.player.getCurrentTime(); const duration = this.player.getDuration(); const progress = (currentTime / duration) * 100; // Update progress in UI and save to local storage this.updateVideoProgress(this.currentVideo?.id, progress); } updateVideoProgress(videoId, progress) { if (!videoId) return; const progressData = JSON.parse(localStorage.getItem('videoProgress') || '{}'); progressData[videoId] = Math.max(progressData[videoId] || 0, progress); localStorage.setItem('videoProgress', JSON.stringify(progressData)); // Update UI progress indicator const progressBar = document.querySelector(`[data-video-id="${videoId}"] .progress-fill`); if (progressBar) { progressBar.style.width = `${progress}%`; } } } class MonacoEditorManager { constructor() { this.editors = new Map(); this.currentLanguage = 'javascript'; this.themes = ['vs-dark', 'vs-light', 'hc-black']; this.currentTheme = 'vs-dark'; } // Load Monaco Editor async loadMonacoEditor() { return new Promise((resolve, reject) => { if (window.monaco) { resolve(window.monaco); return; } // Load Monaco Editor from CDN const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.44.0/min/vs/loader.min.js'; script.onload = () => { require.config({ paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.44.0/min/vs' } }); require(['vs/editor/editor.main'], () => { resolve(window.monaco); }); }; script.onerror = reject; document.head.appendChild(script); }); } // Create Monaco Editor instance async createEditor(containerId, options = {}) { const monaco = await this.loadMonacoEditor(); const container = document.getElementById(containerId); if (!container) { throw new Error(`Container ${containerId} not found`); } const defaultOptions = { language: this.currentLanguage, theme: this.currentTheme, automaticLayout: true, fontSize: 14, lineNumbers: 'on', roundedSelection: false, scrollBeyondLastLine: false, readOnly: false, minimap: { enabled: true }, folding: true, foldingStrategy: 'indentation', showFoldingControls: 'always', wordWrap: 'on', cursorStyle: 'line', insertSpaces: true, tabSize: 2, detectIndentation: true, trimAutoWhitespace: true, formatOnPaste: true, formatOnType: true, autoIndent: 'advanced', suggestOnTriggerCharacters: true, acceptSuggestionOnEnter: 'on', quickSuggestions: true, ...options }; const editor = monaco.editor.create(container, defaultOptions); this.editors.set(containerId, editor); // Add custom key bindings this.addCustomKeyBindings(editor, monaco); // Set up language-specific features this.setupLanguageFeatures(editor, monaco); return editor; } addCustomKeyBindings(editor, monaco) { // Add custom shortcuts editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyR, () => { this.runCode(editor); }); editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => { this.saveCode(editor); }); editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyF, () => { this.formatCode(editor); }); } setupLanguageFeatures(editor, monaco) { // Add custom snippets for DevOps languages monaco.languages.registerCompletionItemProvider('yaml', { provideCompletionItems: (model, position) => { return { suggestions: this.getKubernetesSnippets(monaco, model, position) }; } }); monaco.languages.registerCompletionItemProvider('dockerfile', { provideCompletionItems: (model, position) => { return { suggestions: this.getDockerfileSnippets(monaco, model, position) }; } }); } getKubernetesSnippets(monaco, model, position) { const word = model.getWordUntilPosition(position); const range = { startLineNumber: position.lineNumber, endLineNumber: position.lineNumber, startColumn: word.startColumn, endColumn: word.endColumn }; return [ { label: 'k8s-deployment', kind: monaco.languages.CompletionItemKind.Snippet, insertText: `apiVersion: apps/v1 kind: Deployment metadata: name: \${1:app-name} labels: app: \${1:app-name} spec: replicas: \${2:3} selector: matchLabels: app: \${1:app-name} template: metadata: labels: app: \${1:app-name} spec: containers: - name: \${1:app-name} image: \${3:nginx:latest} ports: - containerPort: \${4:80}`, insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, documentation: 'Kubernetes Deployment template', range: range }, { label: 'k8s-service', kind: monaco.languages.CompletionItemKind.Snippet, insertText: `apiVersion: v1 kind: Service metadata: name: \${1:app-service} spec: selector: app: \${2:app-name} ports: - protocol: TCP port: \${3:80} targetPort: \${4:80} type: \${5:ClusterIP}`, insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, documentation: 'Kubernetes Service template', range: range } ]; } getDockerfileSnippets(monaco, model, position) { const word = model.getWordUntilPosition(position); const range = { startLineNumber: position.lineNumber, endLineNumber: position.lineNumber, startColumn: word.startColumn, endColumn: word.endColumn }; return [ { label: 'dockerfile-node', kind: monaco.languages.CompletionItemKind.Snippet, insertText: `FROM node:\${1:18-alpine} WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . EXPOSE \${2:3000} USER node CMD ["npm", "start"]`, insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, documentation: 'Node.js Dockerfile template', range: range } ]; } changeLanguage(editorId, language) { const editor = this.editors.get(editorId); if (editor) { const monaco = window.monaco; monaco.editor.setModelLanguage(editor.getModel(), language); this.currentLanguage = language; } } changeTheme(theme) { const monaco = window.monaco; if (monaco && this.themes.includes(theme)) { monaco.editor.setTheme(theme); this.currentTheme = theme; } } async runCode(editor) { const code = editor.getValue(); const language = editor.getModel().getLanguageId(); // Show output panel this.showOutput('Running code...'); try { let result; switch (language) { case 'javascript': result = await this.runJavaScript(code); break; case 'python': result = await this.runPython(code); break; case 'yaml': result = await this.validateYAML(code); break; case 'dockerfile': result = await this.validateDockerfile(code); break; default: result = 'Language execution not supported yet'; } this.showOutput(result); } catch (error) { this.showOutput(`Error: ${error.message}`, 'error'); } } async runJavaScript(code) { const logs = []; const mockConsole = { log: (...args) => logs.push(args.join(' ')), error: (...args) => logs.push('ERROR: ' + args.join(' ')), warn: (...args) => logs.push('WARNING: ' + args.join(' ')) }; try { const func = new Function('console', code); const result = func(mockConsole); return logs.join('\n') || `Execution completed. ${result !== undefined ? 'Result: ' + result : ''}`; } catch (error) { return `JavaScript Error: ${error.message}`; } } async runPython(code) { // This would require a Python execution service return 'Python execution requires backend service (Pyodide integration coming soon)'; } async validateYAML(code) { try { // Basic YAML validation (you might want to use a proper YAML parser) const lines = code.split('\n'); const errors = []; lines.forEach((line, index) => { if (line.trim() && !line.match(/^(\s*#|\s*$|\s*\w+\s*:|\s*-\s)/)) { errors.push(`Line ${index + 1}: Invalid YAML syntax`); } }); if (errors.length > 0) { return 'YAML Validation Errors:\n' + errors.join('\n'); } return 'YAML is valid! ✅'; } catch (error) { return `YAML Error: ${error.message}`; } } async validateDockerfile(code) { const lines = code.split('\n').filter(line => line.trim()); const errors = []; if (!lines.some(line => line.trim().toUpperCase().startsWith('FROM'))) { errors.push('Dockerfile must start with FROM instruction'); } lines.forEach((line, index) => { const instruction = line.trim().split(' ')[0].toUpperCase(); const validInstructions = ['FROM', 'RUN', 'CMD', 'ENTRYPOINT', 'COPY', 'ADD', 'WORKDIR', 'EXPOSE', 'ENV', 'ARG', 'LABEL', 'USER', 'VOLUME', 'ONBUILD', 'STOPSIGNAL', 'HEALTHCHECK', 'SHELL']; if (instruction && !instruction.startsWith('#') && !validInstructions.includes(instruction)) { errors.push(`Line ${index + 1}: Unknown instruction '${instruction}'`); } }); if (errors.length > 0) { return 'Dockerfile Validation Errors:\n' + errors.join('\n'); } return 'Dockerfile is valid! ✅'; } showOutput(content, type = 'info') { let outputContainer = document.getElementById('editorOutput'); if (!outputContainer) { outputContainer = document.createElement('div'); outputContainer.id = 'editorOutput'; outputContainer.className = 'editor-output'; outputContainer.innerHTML = ` <div class="output-header"> <h4>Output</h4> <button onclick="monacoManager.closeOutput()" class="btn-close">×</button> </div> <pre class="output-content"></pre> `; const editorContainer = document.querySelector('.editor-container'); if (editorContainer) { editorContainer.appendChild(outputContainer); } } const outputContent = outputContainer.querySelector('.output-content'); outputContent.textContent = content; outputContent.className = `output-content output-${type}`; outputContainer.style.display = 'block'; } closeOutput() { const outputContainer = document.getElementById('editorOutput'); if (outputContainer) { outputContainer.style.display = 'none'; } } saveCode(editor) { const code = editor.getValue(); const language = editor.getModel().getLanguageId(); const timestamp = new Date().toISOString(); const savedCode = { code, language, timestamp, id: Date.now().toString() }; const savedCodes = JSON.parse(localStorage.getItem('savedCodes') || '[]'); savedCodes.unshift(savedCode); // Keep only last 10 saves if (savedCodes.length > 10) { savedCodes.splice(10); } localStorage.setItem('savedCodes', JSON.stringify(savedCodes)); this.showOutput(`Code saved successfully at ${new Date(timestamp).toLocaleTimeString()}`, 'success'); } formatCode(editor) { editor.getAction('editor.action.formatDocument').run(); this.showOutput('Code formatted successfully', 'success'); } getEditor(containerId) { return this.editors.get(containerId); } destroyEditor(containerId) { const editor = this.editors.get(containerId); if (editor) { editor.dispose(); this.editors.delete(containerId); } } getAllSavedCodes() { return JSON.parse(localStorage.getItem('savedCodes') || '[]'); } loadSavedCode(codeId, editorId) { const savedCodes = this.getAllSavedCodes(); const code = savedCodes.find(c => c.id === codeId); if (code) { const editor = this.getEditor(editorId); if (editor) { editor.setValue(code.code); this.changeLanguage(editorId, code.language); this.showOutput(`Loaded code from ${new Date(code.timestamp).toLocaleString()}`, 'success'); } } } } // Initialize managers const youTubeManager = new YouTubePlayerManager(); const monacoManager = new MonacoEditorManager(); // Export for global use window.youTubeManager = youTubeManager; window.monacoManager = monacoManager; // CSS for additional components const additionalCSS = ` .youtube-overlay { position: absolute; top: 10px; right: 10px; z-index: 10; } .video-overlay-controls { display: flex; gap: 0.5rem; flex-direction: column; } .overlay-btn { background: rgba(0, 0, 0, 0.7); color: white; border: none; padding: 0.5rem; border-radius: 0.25rem; cursor: pointer; font-size: 0.8rem; transition: all 0.3s ease; } .overlay-btn:hover { background: rgba(0, 255, 0, 0.8); color: black; } .notification { position: fixed; top: 100px; right: 20px; background: rgba(0, 0, 0, 0.9); color: white; padding: 1rem; border-radius: 0.5rem; border-left: 4px solid var(--primary-color); z-index: 1000; animation: slideInRight 0.3s ease; max-width: 300px; } .notification-success { border-left-color: var(--success-color); } .notification-error { border-left-color: var(--danger-color); } @keyframes slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } .editor-output { background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 0.5rem; margin-top: 1rem; display: none; } .output-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid var(--border-color); } .output-header h4 { margin: 0; color: var(--primary-color); } .btn-close { background: none; border: none; color: var(--text-muted); font-size: 1.5rem; cursor: pointer; padding: 0; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: all 0.3s ease; } .btn-close:hover { background: var(--danger-color); color: white; } .output-content { padding: 1rem; margin: 0; font-family: 'Fira Code', monospace; font-size: 0.9rem; white-space: pre-wrap; color: var(--text-secondary); } .output-content.output-error { color: var(--danger-color); } .output-content.output-success { color: var(--success-color); } `; // Add CSS to document const style = document.createElement('style'); style.textContent = additionalCSS; document.head.appendChild(style); export { youTubeManager, monacoManager };