← back to demon-of-fire__accessable-studio

Function bodies 189 total

All specs Real LLM only Function bodies
findFFmpeg function · javascript · L10-L64 (55 LOC)
main.js
function findFFmpeg() {
  // First try to find via 'where' command (searches PATH)
  try {
    const result = require('child_process').execSync('where ffmpeg', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
    if (result) {
      const firstPath = result.split('\n')[0].trim();
      if (firstPath) return firstPath;
    }
  } catch (e) { /* not in PATH */ }

  // Search common locations
  const possible = [
    path.join(process.env.LOCALAPPDATA || '', 'Microsoft', 'WinGet', 'Links', 'ffmpeg.exe'),
    'C:\\ffmpeg\\bin\\ffmpeg.exe',
    path.join(process.env.ProgramFiles || '', 'ffmpeg', 'bin', 'ffmpeg.exe'),
    path.join(process.env.ProgramFiles || '', 'ffmpeg', 'ffmpeg.exe'),
  ];

  // Also search WinGet packages folder recursively
  const wingetPkgs = path.join(process.env.LOCALAPPDATA || '', 'Microsoft', 'WinGet', 'Packages');
  try {
    if (fs.existsSync(wingetPkgs)) {
      const dirs = fs.readdirSync(wingetPkgs).filter(d => d.toLowerCase().includes('ffmpeg'
createWindow function · javascript · L66-L134 (69 LOC)
main.js
function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1400,
    height: 900,
    minWidth: 800,
    minHeight: 600,
    title: 'Accessible Studio',
    icon: path.join(__dirname, 'src', 'assets', 'icon.png'),
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false,
      webSecurity: false,  // Allow file:// images on canvas without tainting (needed for photo editor)
    },
  });

  mainWindow.loadFile(path.join(__dirname, 'src', 'index.html'));

  // Build accessible menu
  const menuTemplate = [
    {
      label: '&File',
      submenu: [
        { label: '&New Project', accelerator: 'CmdOrCtrl+N', click: () => mainWindow.webContents.send('menu-action', 'new-project') },
        { label: '&Open Project', accelerator: 'CmdOrCtrl+O', click: () => handleOpenProject() },
        { label: '&Save Project', accelerator: 'CmdOrCtrl+S', click: () => mainWindow.webContents.send('menu-action', 'save-p
handleImportMedia function · javascript · L137-L150 (14 LOC)
main.js
async function handleImportMedia() {
  const result = await dialog.showOpenDialog(mainWindow, {
    title: 'Import Media Files',
    filters: [
      { name: 'Video Files', extensions: ['mp4', 'avi', 'mkv', 'mov', 'wmv', 'flv', 'webm', 'mpeg', 'mpg', 'm4v'] },
      { name: 'Image Files', extensions: ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'tiff', 'svg'] },
      { name: 'All Files', extensions: ['*'] },
    ],
    properties: ['openFile', 'multiSelections'],
  });
  if (!result.canceled && result.filePaths.length > 0) {
    mainWindow.webContents.send('files-imported', result.filePaths);
  }
}
handleImportAudio function · javascript · L152-L164 (13 LOC)
main.js
async function handleImportAudio() {
  const result = await dialog.showOpenDialog(mainWindow, {
    title: 'Import Audio Files',
    filters: [
      { name: 'Audio Files', extensions: ['mp3', 'wav', 'ogg', 'flac', 'aac', 'wma', 'm4a'] },
      { name: 'All Files', extensions: ['*'] },
    ],
    properties: ['openFile', 'multiSelections'],
  });
  if (!result.canceled && result.filePaths.length > 0) {
    mainWindow.webContents.send('audio-imported', result.filePaths);
  }
}
handleOpenProject function · javascript · L166-L176 (11 LOC)
main.js
async function handleOpenProject() {
  const result = await dialog.showOpenDialog(mainWindow, {
    title: 'Open Project',
    filters: [{ name: 'Accessible Studio Project', extensions: ['asproj'] }],
    properties: ['openFile'],
  });
  if (!result.canceled && result.filePaths.length > 0) {
    const data = fs.readFileSync(result.filePaths[0], 'utf-8');
    mainWindow.webContents.send('project-loaded', JSON.parse(data));
  }
}
getSourceContent function · javascript · L272-L290 (19 LOC)
main.js
  async function getSourceContent() {
    if (inputExt === 'docx') {
      try {
        const mammoth = require('mammoth');
        const htmlResult = await mammoth.convertToHtml({ path: inputPath });
        const textResult = await mammoth.extractRawText({ path: inputPath });
        return { html: htmlResult.value, text: textResult.value };
      } catch (e) {
        throw new Error('Failed to read DOCX: ' + (e.message || e));
      }
    }
    const raw = fs.readFileSync(inputPath, 'utf-8');
    if (inputExt === 'html' || inputExt === 'htm') {
      return { html: raw, text: raw.replace(/<[^>]*>/g, '') };
    }
    // txt, md, rtf, csv, etc — treat as plain text
    const escaped = raw.replace(/</g, '&lt;').replace(/>/g, '&gt;');
    return { html: `<pre>${escaped}</pre>`, text: raw };
  }
announce function · javascript · L11-L18 (8 LOC)
src/js/accessibility.js
  function announce(message) {
    if (!announcer) return;
    announcer.textContent = '';
    // Force re-announcement by toggling content
    requestAnimationFrame(() => {
      announcer.textContent = message;
    });
  }
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
announceStatus function · javascript · L21-L27 (7 LOC)
src/js/accessibility.js
  function announceStatus(message) {
    if (!status) return;
    status.textContent = '';
    requestAnimationFrame(() => {
      status.textContent = message;
    });
  }
setStatus function · javascript · L30-L34 (5 LOC)
src/js/accessibility.js
  function setStatus(message) {
    const statusEl = document.getElementById('status-text');
    if (statusEl) statusEl.textContent = message;
    announceStatus(message);
  }
formatTime function · javascript · L37-L43 (7 LOC)
src/js/accessibility.js
  function formatTime(seconds) {
    if (isNaN(seconds) || seconds < 0) return '0 seconds';
    const mins = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    if (mins === 0) return `${secs} second${secs !== 1 ? 's' : ''}`;
    return `${mins} minute${mins !== 1 ? 's' : ''} ${secs} second${secs !== 1 ? 's' : ''}`;
  }
formatTimeDisplay function · javascript · L46-L51 (6 LOC)
src/js/accessibility.js
  function formatTimeDisplay(seconds) {
    if (isNaN(seconds) || seconds < 0) return '00:00';
    const mins = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
  }
trapFocus function · javascript · L54-L79 (26 LOC)
src/js/accessibility.js
  function trapFocus(element) {
    const focusableSelectors = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
    const focusableElements = element.querySelectorAll(focusableSelectors);
    const firstFocusable = focusableElements[0];
    const lastFocusable = focusableElements[focusableElements.length - 1];

    function handleTab(e) {
      if (e.key !== 'Tab') return;
      if (e.shiftKey) {
        if (document.activeElement === firstFocusable) {
          e.preventDefault();
          lastFocusable.focus();
        }
      } else {
        if (document.activeElement === lastFocusable) {
          e.preventDefault();
          firstFocusable.focus();
        }
      }
    }

    element.addEventListener('keydown', handleTab);
    if (firstFocusable) firstFocusable.focus();

    return () => element.removeEventListener('keydown', handleTab);
  }
handleTab function · javascript · L60-L73 (14 LOC)
src/js/accessibility.js
    function handleTab(e) {
      if (e.key !== 'Tab') return;
      if (e.shiftKey) {
        if (document.activeElement === firstFocusable) {
          e.preventDefault();
          lastFocusable.focus();
        }
      } else {
        if (document.activeElement === lastFocusable) {
          e.preventDefault();
          firstFocusable.focus();
        }
      }
    }
showModal function · javascript · L82-L89 (8 LOC)
src/js/accessibility.js
  function showModal(modalEl) {
    modalEl.classList.remove('hidden');
    modalEl.setAttribute('aria-hidden', 'false');
    const cleanup = trapFocus(modalEl);
    modalEl._focusCleanup = cleanup;
    modalEl._previousFocus = document.activeElement;
    announce('Dialog opened: ' + (modalEl.querySelector('h2')?.textContent || 'Dialog'));
  }
hideModal function · javascript · L92-L98 (7 LOC)
src/js/accessibility.js
  function hideModal(modalEl) {
    modalEl.classList.add('hidden');
    modalEl.setAttribute('aria-hidden', 'true');
    if (modalEl._focusCleanup) modalEl._focusCleanup();
    if (modalEl._previousFocus) modalEl._previousFocus.focus();
    announce('Dialog closed');
  }
Repobility · MCP-ready · https://repobility.com
setupToolbarNavigation function · javascript · L101-L134 (34 LOC)
src/js/accessibility.js
  function setupToolbarNavigation(toolbar) {
    const buttons = toolbar.querySelectorAll('button');
    let currentIndex = 0;

    toolbar.addEventListener('keydown', (e) => {
      let newIndex = currentIndex;
      switch (e.key) {
        case 'ArrowRight':
        case 'ArrowDown':
          e.preventDefault();
          newIndex = (currentIndex + 1) % buttons.length;
          break;
        case 'ArrowLeft':
        case 'ArrowUp':
          e.preventDefault();
          newIndex = (currentIndex - 1 + buttons.length) % buttons.length;
          break;
        case 'Home':
          e.preventDefault();
          newIndex = 0;
          break;
        case 'End':
          e.preventDefault();
          newIndex = buttons.length - 1;
          break;
        default:
          return;
      }
      buttons[currentIndex].setAttribute('tabindex', '-1');
      buttons[newIndex].setAttribute('tabindex', '0');
      buttons[newIndex].focus();
      currentIndex = newIndex;
    });
  }
setupListboxNavigation function · javascript · L137-L181 (45 LOC)
src/js/accessibility.js
  function setupListboxNavigation(listbox, onSelect) {
    listbox.addEventListener('keydown', (e) => {
      const items = listbox.querySelectorAll('[role="option"]');
      if (items.length === 0) return;

      const currentItem = listbox.querySelector('[aria-selected="true"]') || items[0];
      let currentIdx = Array.from(items).indexOf(currentItem);
      let newIdx = currentIdx;

      switch (e.key) {
        case 'ArrowDown':
          e.preventDefault();
          newIdx = Math.min(currentIdx + 1, items.length - 1);
          break;
        case 'ArrowUp':
          e.preventDefault();
          newIdx = Math.max(currentIdx - 1, 0);
          break;
        case 'Home':
          e.preventDefault();
          newIdx = 0;
          break;
        case 'End':
          e.preventDefault();
          newIdx = items.length - 1;
          break;
        case 'Enter':
        case ' ':
          e.preventDefault();
          if (onSelect) onSelect(items[currentIdx]);
          retur
initModalEscapeHandler function · javascript · L184-L191 (8 LOC)
src/js/accessibility.js
  function initModalEscapeHandler() {
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape') {
        const openModals = document.querySelectorAll('.modal:not(.hidden)');
        openModals.forEach(modal => hideModal(modal));
      }
    });
  }
searchSounds function · javascript · L39-L85 (47 LOC)
src/js/ai-assistant.js
  function searchSounds(query, type = 'sfx') {
    if (!query) {
      return 'Please specify what sound you want to find. For example: "search for rain sounds" or "find background jazz music"';
    }

    const results = [];

    if (type === 'music' || type === 'background') {
      results.push({
        source: 'Pixabay Music',
        url: SOUND_SOURCES.pixabay.searchUrl(query),
      });
      results.push({
        source: 'Mixkit Music',
        url: SOUND_SOURCES.mixkit.musicUrl(query),
      });
    } else {
      results.push({
        source: 'Pixabay Sound Effects',
        url: SOUND_SOURCES.pixabay.sfxUrl(query),
      });
      results.push({
        source: 'Mixkit Sound Effects',
        url: SOUND_SOURCES.mixkit.searchUrl(query),
      });
    }

    results.push({
      source: 'Freesound',
      url: SOUND_SOURCES.freesound.searchUrl(query),
    });
    results.push({
      source: 'ZapSplat',
      url: SOUND_SOURCES.zapsplat.searchUrl(query),
    });

    // Open
autoColorCorrect function · javascript · L92-L98 (7 LOC)
src/js/ai-assistant.js
  function autoColorCorrect() {
    // Intelligent auto-correction based on common issues
    Effects.setFilter('brightness', 105);
    Effects.setFilter('contrast', 115);
    Effects.setFilter('saturation', 110);
    return 'Applied auto color correction: brightness 105%, contrast 115%, saturation 110%. This brightens dark footage and adds vibrance.';
  }
autoEnhancePhoto function · javascript · L101-L107 (7 LOC)
src/js/ai-assistant.js
  function autoEnhancePhoto() {
    PhotoEditor.setAdjustment('brightness', 10);
    PhotoEditor.setAdjustment('contrast', 15);
    PhotoEditor.setAdjustment('saturation', 10);
    PhotoEditor.setAdjustment('sharpness', 20);
    return 'Applied auto enhancement: +10 brightness, +15 contrast, +10 saturation, +20 sharpness.';
  }
createMontage function · javascript · L110-L124 (15 LOC)
src/js/ai-assistant.js
  function createMontage(clipDuration = 3) {
    const clips = Timeline.getClips().filter(c => c.type === 'video');
    if (clips.length === 0) {
      return 'No video clips on the timeline to create a montage from. Import some videos first.';
    }

    // Trim each clip to the specified duration and space them evenly
    clips.forEach((clip, i) => {
      Timeline.updateClipProperty(clip.id, 'duration', clipDuration);
      Timeline.updateClipProperty(clip.id, 'startTime', i * clipDuration);
    });

    const totalDuration = clips.length * clipDuration;
    return `Created montage: ${clips.length} clips, each ${clipDuration} seconds long. Total duration: ${totalDuration} seconds.`;
  }
speedRamp function · javascript · L127-L160 (34 LOC)
src/js/ai-assistant.js
  function speedRamp(style = 'dramatic') {
    const clip = Timeline.getSelectedClip();
    if (!clip) return 'No clip selected. Select a clip first.';

    // Split clip into segments with varying speeds
    const duration = clip.duration;
    if (duration < 4) return 'Clip is too short for speed ramping. Need at least 4 seconds.';

    const thirdDuration = duration / 3;

    switch (style) {
      case 'dramatic':
        // Slow-normal-slow
        Timeline.splitClip(clip.id, clip.startTime + thirdDuration);
        const clips2 = Timeline.getClips();
        const secondClip = clips2[clips2.length - 1];
        Timeline.splitClip(secondClip.id, secondClip.startTime + thirdDuration);
        const clips3 = Timeline.getClips();

        // Set speeds
        Timeline.updateClipProperty(clip.id, 'speed', 0.5);
        Timeline.updateClipProperty(clips3[clips3.length - 2].id, 'speed', 1);
        Timeline.updateClipProperty(clips3[clips3.length - 1].id, 'speed', 0.5);
        return '
If a scraper extracted this row, it came from Repobility (https://repobility.com)
autoTrimSilence function · javascript · L163-L170 (8 LOC)
src/js/ai-assistant.js
  function autoTrimSilence() {
    const clip = Timeline.getSelectedClip();
    if (!clip) return 'No clip selected.';

    // Trim 0.5s from start and end (common dead space)
    Timeline.trimClip(clip.id, 0.5, 0.5);
    return `Trimmed 0.5 seconds from start and end of "${clip.name}" to remove dead space.`;
  }
suggestEdits function · javascript · L173-L222 (50 LOC)
src/js/ai-assistant.js
  function suggestEdits() {
    const clips = Timeline.getClips();
    if (clips.length === 0) {
      return 'No clips on the timeline. Import some media first, then I can suggest edits.';
    }

    const suggestions = [];

    // Check total duration
    const totalDuration = Timeline.getTotalDuration();
    if (totalDuration > 300) {
      suggestions.push('Your video is over 5 minutes. Consider trimming clips or increasing playback speed for better engagement.');
    }
    if (totalDuration < 10) {
      suggestions.push('Your video is very short. Consider adding more clips or slowing down the playback speed.');
    }

    // Check for gaps
    const videoClips = clips.filter(c => c.type === 'video').sort((a, b) => a.startTime - b.startTime);
    for (let i = 1; i < videoClips.length; i++) {
      const gap = videoClips[i].startTime - (videoClips[i-1].startTime + videoClips[i-1].duration);
      if (gap > 1) {
        suggestions.push(`There's a ${gap.toFixed(1)} second gap betwee
closeGaps function · javascript · L225-L244 (20 LOC)
src/js/ai-assistant.js
  function closeGaps() {
    const clips = Timeline.getClips();
    const types = ['video', 'audio', 'text'];

    let gapsClosed = 0;
    types.forEach(type => {
      const trackClips = clips.filter(c => c.type === type).sort((a, b) => a.startTime - b.startTime);
      let currentEnd = 0;
      trackClips.forEach(clip => {
        if (clip.startTime > currentEnd) {
          gapsClosed++;
          Timeline.updateClipProperty(clip.id, 'startTime', currentEnd);
        }
        currentEnd = clip.startTime + clip.duration;
      });
    });

    if (gapsClosed === 0) return 'No gaps found between clips.';
    return `Closed ${gapsClosed} gap${gapsClosed > 1 ? 's' : ''} between clips. All clips are now seamlessly connected.`;
  }
addFadeEffect function · javascript · L247-L261 (15 LOC)
src/js/ai-assistant.js
  function addFadeEffect(type = 'both') {
    const clip = Timeline.getSelectedClip();
    if (!clip) return 'No clip selected.';

    // Store fade info on the clip for export
    if (type === 'in' || type === 'both') {
      clip.fadeIn = true;
    }
    if (type === 'out' || type === 'both') {
      clip.fadeOut = true;
    }

    const fadeDesc = type === 'both' ? 'fade in and fade out' : `fade ${type}`;
    return `Added ${fadeDesc} effect to "${clip.name}". This will be applied during export.`;
  }
processAICommand function · javascript · L267-L371 (105 LOC)
src/js/ai-assistant.js
  function processAICommand(text) {
    const lower = text.toLowerCase().trim();

    // SEARCH FOR SOUNDS
    const searchSFXMatch = lower.match(/(?:search|find|look for|get)\s+(?:a\s+)?(?:sound\s+(?:effect|fx)|sfx|sound)\s+(?:of\s+|for\s+)?(.+)/);
    const searchMusicMatch = lower.match(/(?:search|find|look for|get)\s+(?:a\s+)?(?:background\s+)?(?:music|track|song|soundtrack)\s+(?:of\s+|for\s+|about\s+)?(.+)/);
    const searchAnyMatch = lower.match(/(?:search|find|look for)\s+(.+?)(?:\s+(?:sound|music|track|audio))?$/);

    if (searchSFXMatch) {
      const query = searchSFXMatch[1];
      const results = searchSounds(query, 'sfx');
      let msg = `Searching for "${query}" sound effects. I've opened <strong>${results[0].source}</strong> in your browser.<br><br>Other sources to try:`;
      msg += '<ul>' + results.slice(1).map(r => `<li><strong>${r.source}</strong></li>`).join('') + '</ul>';
      msg += 'Download the sound file, then use <strong>Import Audio</strong> to add it to
switchSection function · javascript · L18-L54 (37 LOC)
src/js/app.js
  function switchSection(sectionId) {
    // Hide all sections
    document.querySelectorAll('.app-section').forEach(s => {
      s.style.display = 'none';
    });
    // Deactivate all nav buttons
    document.querySelectorAll('.nav-button').forEach(b => {
      b.classList.remove('active');
      b.setAttribute('aria-pressed', 'false');
    });
    // Show target section
    const section = document.getElementById(sectionId);
    if (section) {
      section.style.display = 'flex';
    }
    // Activate nav button
    for (const [btnId, secId] of Object.entries(navButtons)) {
      if (secId === sectionId) {
        const btn = document.getElementById(btnId);
        if (btn) {
          btn.classList.add('active');
          btn.setAttribute('aria-pressed', 'true');
        }
        break;
      }
    }

    const sectionNames = {
      'section-video-editor': 'Video Editor',
      'section-photo-editor': 'Photo Editor',
      'section-file-converter': 'File Converter',
      'sect
initNavigation function · javascript · L56-L63 (8 LOC)
src/js/app.js
  function initNavigation() {
    for (const [btnId, sectionId] of Object.entries(navButtons)) {
      const btn = document.getElementById(btnId);
      if (btn) {
        btn.addEventListener('click', () => switchSection(sectionId));
      }
    }
  }
addMediaToLibrary function · javascript · L80-L147 (68 LOC)
src/js/app.js
  function addMediaToLibrary(filePaths) {
    for (const fp of filePaths) {
      try {
        const fileName = fp.split(/[\\/]/).pop();
        const ext = fileName.split('.').pop().toLowerCase();
        const isVideo = ['mp4', 'avi', 'mkv', 'mov', 'wmv', 'flv', 'webm', 'mpeg', 'mpg', 'm4v'].includes(ext);
        const isAudio = ['mp3', 'wav', 'ogg', 'flac', 'aac', 'wma', 'm4a'].includes(ext);
        const isImage = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'tiff', 'svg'].includes(ext);

        let type = 'video';
        if (isAudio) type = 'audio';
        else if (isImage) type = 'image';

        // Add to media library immediately with placeholder duration
        const libEntry = { path: fp, name: fileName, type, duration: isImage ? 5 : 10 };
        mediaLibrary.push(libEntry);
        renderMediaLibrary();

        const trackType = isAudio ? 'audio' : 'video';

        if (isVideo || isAudio) {
          // Get real duration from a temporary media element before placi
Open data scored by Repobility · https://repobility.com
showVideoPlayer function · javascript · L150-L155 (6 LOC)
src/js/app.js
  function showVideoPlayer() {
    const wrapper = document.getElementById('video-player-wrapper');
    const noVideoMsg = document.getElementById('no-video-message');
    if (wrapper) wrapper.classList.remove('hidden');
    if (noVideoMsg) noVideoMsg.style.display = 'none';
  }
showPlacementDialog function · javascript · L158-L251 (94 LOC)
src/js/app.js
  function showPlacementDialog() {
    console.log('showPlacementDialog called, pending:', pendingFiles.length);
    if (pendingFiles.length === 0) return;

    currentPendingFile = pendingFiles[0];
    const dialog = document.getElementById('placement-dialog');
    const nameEl = document.getElementById('placement-file-name');
    const infoEl = document.getElementById('placement-file-info');

    if (!dialog) {
      console.error('Placement dialog element not found!');
      // Fallback: just add to end of track
      const file = pendingFiles.shift();
      if (file) {
        Timeline.addClip({
          name: file.fileName,
          type: file.trackType,
          filePath: file.filePath,
          duration: file.duration,
        });
        Accessibility.announce(file.fileName + ' added to timeline.');
      }
      currentPendingFile = null;
      return;
    }

    try {
      // Update dialog text
      const playheadTime = Player.getCurrentTime ? Player.getCurrentTime() : 
placePendingFile function · javascript · L255-L287 (33 LOC)
src/js/app.js
  function placePendingFile(startTime) {
    if (!currentPendingFile || isPlacing) return;
    isPlacing = true;

    const file = currentPendingFile;

    // Remove from queue FIRST to prevent double-add
    pendingFiles.shift();
    currentPendingFile = null;

    Timeline.addClip({
      name: file.fileName,
      type: file.trackType,
      filePath: file.filePath,
      duration: file.duration,
      startTime,
    });

    const placedAt = Accessibility.formatTime(startTime);
    Accessibility.announce(
      `${file.fileName} placed on ${file.trackType} track at ${placedAt}. Duration: ${Accessibility.formatTime(file.duration)}.`
    );

    // Close dialog
    Accessibility.hideModal(document.getElementById('placement-dialog'));

    isPlacing = false;

    // Show next if there are more
    if (pendingFiles.length > 0) {
      setTimeout(showPlacementDialog, 300);
    }
  }
initPlacementDialog function · javascript · L290-L328 (39 LOC)
src/js/app.js
  function initPlacementDialog() {
    document.getElementById('placement-start-track')?.addEventListener('click', () => {
      placePendingFile(0);
    });

    document.getElementById('placement-playhead')?.addEventListener('click', () => {
      placePendingFile(Player.getCurrentTime());
    });

    document.getElementById('placement-end-track')?.addEventListener('click', () => {
      if (!currentPendingFile) return;
      placePendingFile(Timeline.getTrackEndTime(currentPendingFile.trackType));
    });

    document.getElementById('placement-after-selected')?.addEventListener('click', () => {
      const clip = Timeline.getSelectedClip();
      if (clip) {
        placePendingFile(clip.startTime + clip.duration);
      }
    });

    document.getElementById('placement-before-selected')?.addEventListener('click', () => {
      const clip = Timeline.getSelectedClip();
      if (clip) {
        placePendingFile(clip.startTime);
      }
    });

    document.getElementById('placemen
renderMediaLibrary function · javascript · L330-L370 (41 LOC)
src/js/app.js
  function renderMediaLibrary() {
    const list = document.getElementById('media-list');
    const emptyMsg = document.getElementById('media-empty-message');
    if (!list) return;

    // Clear
    list.innerHTML = '';
    if (mediaLibrary.length === 0) {
      if (emptyMsg) {
        list.appendChild(emptyMsg);
        emptyMsg.style.display = 'block';
      }
      return;
    }

    mediaLibrary.forEach((item, idx) => {
      const el = document.createElement('div');
      el.className = 'media-item';
      el.setAttribute('role', 'listitem');
      el.setAttribute('tabindex', '0');
      el.setAttribute('aria-label', `${item.name}, ${item.type}, ${item.duration > 0 ? 'duration ' + Accessibility.formatTime(item.duration) : 'image'}`);
      el.setAttribute('aria-selected', idx === selectedMediaIndex ? 'true' : 'false');

      const typeIcon = item.type === 'video' ? '[V]' : item.type === 'audio' ? '[A]' : '[I]';

      el.innerHTML = `
        <span class="media-name">${typeIcon}
selectMedia function · javascript · L372-L392 (21 LOC)
src/js/app.js
  function selectMedia(index) {
    selectedMediaIndex = index;
    const addBtn = document.getElementById('btn-add-to-timeline');
    if (addBtn) addBtn.disabled = false;

    // Update selection visuals
    document.querySelectorAll('.media-item').forEach((el, i) => {
      el.setAttribute('aria-selected', i === index ? 'true' : 'false');
      el.classList.toggle('selected', i === index);
    });

    const item = mediaLibrary[index];
    if (item) {
      // Load preview
      if (item.type === 'video') {
        Player.loadVideo(item.path);
        showVideoPlayer();
      }
      Accessibility.announce(`Selected: ${item.name}`);
    }
  }
addSelectedToTimeline function · javascript · L394-L406 (13 LOC)
src/js/app.js
  function addSelectedToTimeline() {
    if (selectedMediaIndex < 0 || selectedMediaIndex >= mediaLibrary.length) {
      Accessibility.announce('No media selected. Select a file from the media library first.');
      return;
    }
    const item = mediaLibrary[selectedMediaIndex];
    Timeline.addClip({
      name: item.name,
      type: item.type === 'image' ? 'video' : item.type,
      filePath: item.path,
      duration: item.duration || 5,
    });
  }
initVideoToolbar function · javascript · L411-L647 (237 LOC)
src/js/app.js
  function initVideoToolbar() {
    // Import media
    document.getElementById('btn-import-media')?.addEventListener('click', async () => {
      if (window.api) {
        const result = await window.api.importMedia();
        if (!result.canceled && result.filePaths.length > 0) {
          addMediaToLibrary(result.filePaths);
        }
      }
    });

    // Import audio
    document.getElementById('btn-import-audio')?.addEventListener('click', async () => {
      if (window.api) {
        const result = await window.api.importAudio();
        if (!result.canceled && result.filePaths.length > 0) {
          addMediaToLibrary(result.filePaths);
        }
      }
    });

    // New project
    document.getElementById('btn-new-project')?.addEventListener('click', () => {
      Timeline.clearAll();
      mediaLibrary = [];
      selectedMediaIndex = -1;
      renderMediaLibrary();
      Accessibility.announce('New project created');
      Accessibility.setStatus('New project');
    });
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
initPhotoToolbar function · javascript · L652-L767 (116 LOC)
src/js/app.js
  function initPhotoToolbar() {
    document.getElementById('btn-photo-open')?.addEventListener('click', async () => {
      if (!window.api) return;
      const result = await window.api.showOpenDialog({
        title: 'Open Image',
        filters: [
          { name: 'Image Files', extensions: ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'tiff'] },
        ],
        properties: ['openFile'],
      });
      if (!result.canceled && result.filePaths.length > 0) {
        PhotoEditor.loadImage(result.filePaths[0]);
      }
    });

    document.getElementById('btn-photo-save')?.addEventListener('click', async () => {
      if (!PhotoEditor.hasImage) {
        Accessibility.announce('No image to save');
        return;
      }
      Accessibility.announce('Use Save As to choose format and location');
    });

    document.getElementById('btn-photo-save-as')?.addEventListener('click', async () => {
      if (!PhotoEditor.hasImage || !window.api) return;
      const result = await window
initExportDialog function · javascript · L772-L830 (59 LOC)
src/js/app.js
  function initExportDialog() {
    document.getElementById('btn-export-start')?.addEventListener('click', async () => {
      const format = document.getElementById('export-format')?.value || 'mp4';
      const resolution = document.getElementById('export-resolution')?.value || '1920:1080';
      const fps = document.getElementById('export-fps')?.value || '30';
      const quality = document.getElementById('export-quality')?.value || 'medium';

      const clips = Timeline.getVideoClips();
      if (clips.length === 0) {
        Accessibility.announce('No clips to export. Add media to the timeline first.');
        return;
      }

      if (!window.api) {
        Accessibility.announce('Export requires the desktop application');
        return;
      }

      const result = await window.api.showSaveDialog({
        title: 'Export Video',
        defaultPath: `output.${format}`,
        filters: [{ name: `${format.toUpperCase()} Video`, extensions: [format] }],
      });

      if (re
initTextDialog function · javascript · L835-L867 (33 LOC)
src/js/app.js
  function initTextDialog() {
    document.getElementById('btn-text-add')?.addEventListener('click', () => {
      const text = document.getElementById('text-content-input')?.value || 'Text';
      const fontSize = parseInt(document.getElementById('text-font-size-input')?.value) || 48;
      const color = document.getElementById('text-color-input')?.value || '#ffffff';
      const position = document.getElementById('text-position-select')?.value || 'center';
      const duration = parseFloat(document.getElementById('text-duration-input')?.value) || 5;

      // Place text at playhead position, or at end of text track
      const playheadTime = Player.getCurrentTime();
      const trackEnd = Timeline.getTrackEndTime('text');
      const placeAt = (playheadTime > 0) ? playheadTime : trackEnd;

      Timeline.addClip({
        name: 'Text: ' + text.substring(0, 20),
        type: 'text',
        text,
        fontSize,
        textColor: color,
        textPosition: position,
        dura
initSettings function · javascript · L872-L959 (88 LOC)
src/js/app.js
  function initSettings() {
    // Theme / Contrast / Font size
    document.getElementById('btn-apply-theme')?.addEventListener('click', () => {
      const theme = document.getElementById('theme-mode-select')?.value || 'dark';
      const contrast = document.getElementById('contrast-level-select')?.value || 'normal';
      const fontSize = document.getElementById('font-size-select')?.value || 'normal';

      // Apply theme
      document.body.classList.remove('light-mode');
      if (theme === 'light') document.body.classList.add('light-mode');

      // Apply contrast
      document.body.classList.remove('high-contrast', 'very-high-contrast');
      if (contrast === 'high') document.body.classList.add('high-contrast');
      else if (contrast === 'very-high') document.body.classList.add('very-high-contrast');

      // Apply font size
      document.body.classList.remove('font-small', 'font-normal', 'font-large', 'font-very-large');
      document.body.classList.add(`font-${fontSiz
initKeyboardShortcuts function · javascript · L964-L1058 (95 LOC)
src/js/app.js
  function initKeyboardShortcuts() {
    document.addEventListener('keydown', (e) => {
      // Don't capture when typing in an input
      const isTyping = ['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement?.tagName);

      // F1 - shortcuts dialog (always works)
      if (e.key === 'F1') {
        e.preventDefault();
        const dialog = document.getElementById('shortcuts-dialog');
        if (dialog) Accessibility.showModal(dialog);
        return;
      }

      // Ctrl combinations (always work)
      if (e.ctrlKey || e.metaKey) {
        switch (e.key.toLowerCase()) {
          case 'z':
            e.preventDefault();
            Timeline.undo();
            return;
          case 'y':
            e.preventDefault();
            Timeline.redo();
            return;
          case 'i':
            e.preventDefault();
            if (e.shiftKey) {
              document.getElementById('btn-import-audio')?.click();
            } else {
              document.getElem
initDialogs function · javascript · L1063-L1071 (9 LOC)
src/js/app.js
  function initDialogs() {
    document.getElementById('btn-keyboard-shortcuts')?.addEventListener('click', () => {
      Accessibility.showModal(document.getElementById('shortcuts-dialog'));
    });
    document.getElementById('btn-shortcuts-close')?.addEventListener('click', () => {
      Accessibility.hideModal(document.getElementById('shortcuts-dialog'));
    });

  }
initIPC function · javascript · L1076-L1106 (31 LOC)
src/js/app.js
  function initIPC() {
    if (!window.api) return;

    window.api.onFilesImported((files) => addMediaToLibrary(files));
    window.api.onAudioImported((files) => addMediaToLibrary(files));
    window.api.onMenuAction((action) => {
      switch (action) {
        case 'new-project': document.getElementById('btn-new-project')?.click(); break;
        case 'save-project': document.getElementById('btn-save-project')?.click(); break;
        case 'export': document.getElementById('btn-export-video')?.click(); break;
        case 'undo': Timeline.undo(); break;
        case 'redo': Timeline.redo(); break;
        case 'split': Timeline.splitAtPlayhead(); break;
        case 'delete-clip':
          const c = Timeline.getSelectedClip();
          if (c) Timeline.removeClip(c.id);
          break;
        case 'duplicate': Timeline.duplicateClip(); break;
        case 'zoom-in': Timeline.zoomIn(); break;
        case 'zoom-out': Timeline.zoomOut(); break;
        case 'toggle-chatbot': docum
loadProjectList function · javascript · L1111-L1197 (87 LOC)
src/js/app.js
  async function loadProjectList() {
    const listEl = document.getElementById('projects-list');
    const emptyMsg = document.getElementById('projects-empty-message');
    if (!listEl || !window.api) return;

    try {
      const projects = await window.api.listProjects();
      listEl.innerHTML = '';

      if (projects.length === 0) {
        if (emptyMsg) {
          listEl.appendChild(emptyMsg);
          emptyMsg.style.display = 'block';
        }
        return;
      }

      projects.forEach(proj => {
        const item = document.createElement('div');
        item.className = 'project-item';
        item.setAttribute('role', 'listitem');
        item.setAttribute('tabindex', '0');
        item.setAttribute('aria-label', `Project: ${proj.name}, saved ${new Date(proj.date).toLocaleDateString()}`);

        const dateStr = new Date(proj.date).toLocaleDateString(undefined, {
          year: 'numeric', month: 'short', day: 'numeric',
          hour: '2-digit', minute: '2-digit',
Repobility · MCP-ready · https://repobility.com
init function · javascript · L1202-L1233 (32 LOC)
src/js/app.js
  function init() {
    initNavigation();
    initVideoToolbar();
    initPhotoToolbar();
    initExportDialog();
    initTextDialog();
    initSettings();
    initKeyboardShortcuts();
    initDialogs();
    initPlacementDialog();
    initIPC();

    // Init sub-modules
    Player.init();
    Effects.init();
    PhotoEditor.init();
    Chatbot.init();
    Converter.init();
    Timeline.initTimelineClick();
    Timeline.renderAllTracks();

    // Setup toolbar navigation for screen readers
    document.querySelectorAll('[role="toolbar"]').forEach(toolbar => {
      Accessibility.setupToolbarNavigation(toolbar);
    });

    initClipList();
    loadProjectList();

    Accessibility.setStatus('Ready. Use the sidebar to navigate between sections.');
    Accessibility.announce('Accessible Studio loaded. Use the sidebar buttons to switch between Video Editor, Photo Editor, File Converter, User Guide, and Settings.');
  }
renderClipList function · javascript · L1241-L1250 (10 LOC)
src/js/app.js
  function renderClipList() {
    const allClips = Timeline.getClips();
    const videoClips = allClips.filter(c => c.type === 'video' || c.type === 'image').sort((a, b) => a.startTime - b.startTime);
    const audioClips = allClips.filter(c => c.type === 'audio').sort((a, b) => a.startTime - b.startTime);
    const selectedClip = Timeline.getSelectedClip();

    renderClipSelect('video-clip-select', videoClips, selectedClip, 'No video clips yet');
    renderClipSelect('audio-clip-select', audioClips, selectedClip, 'No audio clips yet');
    updateClipActionButtons();
  }
renderClipSelect function · javascript · L1252-L1285 (34 LOC)
src/js/app.js
  function renderClipSelect(selectId, clips, selectedClip, emptyMsg) {
    const select = document.getElementById(selectId);
    if (!select) return;
    const prevValue = select.value;
    select.innerHTML = '';

    if (clips.length === 0) {
      const opt = document.createElement('option');
      opt.value = '';
      opt.disabled = true;
      opt.selected = true;
      opt.textContent = emptyMsg;
      select.appendChild(opt);
      return;
    }

    clips.forEach((clip, i) => {
      const opt = document.createElement('option');
      opt.value = clip.id;
      const startStr = Accessibility.formatTimeDisplay(clip.startTime);
      const endTime = clip.startTime + clip.duration;
      const endStr = Accessibility.formatTimeDisplay(endTime);
      opt.textContent = `Clip ${i + 1}: ${clip.name} — ${startStr} to ${endStr}`;
      if (selectedClip && selectedClip.id === clip.id) {
        opt.selected = true;
      }
      select.appendChild(opt);
    });

    // Restore previous s
page 1 / 4next ›