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
JavaScript
// 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);
}
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 };