← back to dgleich__eigendeck

Function bodies 50 total

All specs Real LLM only Function bodies
MathJaxPTSansFont function · javascript · L87-L91 (5 LOC)
mathjax-ptsans-bundle/cjs/chtml.js
    function MathJaxPTSansFont() {
        var _this = _super.apply(this, __spreadArray([], __read(arguments), false)) || this;
        _this.cssFontPrefix = 'LATO';
        return _this;
    }
CommonMathJaxPTSansFontMixin function · javascript · L56-L112 (57 LOC)
mathjax-ptsans-bundle/cjs/common.js
function CommonMathJaxPTSansFontMixin(Base) {
    var _a;
    return _a = (function (_super) {
            __extends(class_1, _super);
            function class_1() {
                return _super !== null && _super.apply(this, arguments) || this;
            }
            return class_1;
        }(Base)),
        _a.defaultVariants = __spreadArray(__spreadArray([], __read(FontData_js_1.FontData.defaultVariants), false), [
            ['-size3', 'normal'],
            ['-size4', 'normal'],
            ['-size5', 'normal'],
            ['-size6', 'normal'],
            ['-size7', 'normal'],
            ['-size8', 'normal'],
            ['-size9', 'normal'],
            ['-size10', 'normal'],
            ['-size11', 'normal'],
            ['-size12', 'normal'],
            ['-size13', 'normal'],
            ['-size14', 'normal'],
            ['-size15', 'normal'],
            ['-tex-calligraphic', 'script'],
            ['-tex-bold-calligraphic', 'bold-script'],
            ['-lf-tp', '
class_1 function · javascript · L60-L62 (3 LOC)
mathjax-ptsans-bundle/cjs/common.js
            function class_1() {
                return _super !== null && _super.apply(this, arguments) || this;
            }
MathJaxPTSansFont function · javascript · L73-L92 (20 LOC)
mathjax-ptsans-bundle/cjs/svg.js
    function MathJaxPTSansFont(options) {
        var e_1, _a;
        if (options === void 0) { options = {}; }
        var _this = _super.call(this, options) || this;
        var CLASS = _this.constructor;
        try {
            for (var _b = __values(Object.keys(_this.variant)), _c = _b.next(); !_c.done; _c = _b.next()) {
                var variant = _c.value;
                _this.variant[variant].cacheID = 'PTSA-' + (CLASS.variantCacheIds[variant] || 'N');
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
            }
            finally { if (e_1) throw e_1.error; }
        }
        return _this;
    }
App function · typescript · L21-L151 (131 LOC)
src/App.tsx
function App() {
  const { isPresenting, showProperties } =
    usePresentationStore();
  const [sidebarWidth, setSidebarWidth] = useState(200);
  const resizeStartX = useRef(0);
  const resizeStartW = useRef(0);

  const handleResizeStart = useCallback((e: React.PointerEvent) => {
    e.preventDefault();
    resizeStartX.current = e.clientX;
    resizeStartW.current = sidebarWidth;
    const handleMove = (me: PointerEvent) => {
      setSidebarWidth(Math.min(400, Math.max(150, resizeStartW.current + me.clientX - resizeStartX.current)));
    };
    const handleUp = () => {
      window.removeEventListener('pointermove', handleMove);
      window.removeEventListener('pointerup', handleUp);
      document.body.style.cursor = '';
      document.body.style.userSelect = '';
    };
    window.addEventListener('pointermove', handleMove);
    window.addEventListener('pointerup', handleUp);
    document.body.style.cursor = 'col-resize';
    document.body.style.userSelect = 'none';
  }, [sidebar
addEntry function · typescript · L13-L21 (9 LOC)
src/components/DebugConsole.tsx
function addEntry(level: LogEntry['level'], args: any[]) {
  const message = args.map((a) =>
    typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)
  ).join(' ');
  const time = new Date().toLocaleTimeString();
  globalLogs.push({ level, message, time });
  if (globalLogs.length > 300) globalLogs.splice(0, globalLogs.length - 300);
  listeners.forEach((fn) => fn());
}
interceptConsole function · typescript · L23-L34 (12 LOC)
src/components/DebugConsole.tsx
function interceptConsole() {
  if (intercepted) return;
  intercepted = true;
  const origLog = console.log;
  const origWarn = console.warn;
  const origError = console.error;
  console.log = (...args) => { origLog.apply(console, args); addEntry('log', args); };
  console.warn = (...args) => { origWarn.apply(console, args); addEntry('warn', args); };
  console.error = (...args) => { origError.apply(console, args); addEntry('error', args); };
  window.addEventListener('error', (e) => addEntry('error', [`Unhandled: ${e.message} at ${e.filename}:${e.lineno}`]));
  window.addEventListener('unhandledrejection', (e) => addEntry('error', [`Unhandled rejection: ${e.reason}`]));
}
Source: Repobility analyzer · https://repobility.com
DebugConsole function · typescript · L38-L91 (54 LOC)
src/components/DebugConsole.tsx
export function DebugConsole() {
  const [visible, setVisible] = useState(false);
  const [, setTick] = useState(0);
  const bottomRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const update = () => setTick((t) => t + 1);
    listeners.add(update);
    return () => { listeners.delete(update); };
  }, []);

  useEffect(() => {
    const handleKey = (e: KeyboardEvent) => {
      if (e.key === 'd' && (e.ctrlKey || e.metaKey) && e.shiftKey) {
        e.preventDefault();
        setVisible((v) => !v);
      }
    };
    const handleCustom = () => setVisible((v) => !v);
    window.addEventListener('keydown', handleKey);
    window.addEventListener('toggle-debug-console', handleCustom);
    return () => {
      window.removeEventListener('keydown', handleKey);
      window.removeEventListener('toggle-debug-console', handleCustom);
    };
  }, []);

  useEffect(() => {
    if (visible) bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [globalLogs.length, visible]);
NotesPanel function · typescript · L4-L31 (28 LOC)
src/components/NotesPanel.tsx
export function NotesPanel() {
  const { presentation, currentSlideIndex, updateSlide } =
    usePresentationStore();
  const [collapsed, setCollapsed] = useState(false);

  const slide = presentation.slides[currentSlideIndex];
  if (!slide) return null;

  return (
    <div className={`notes-panel ${collapsed ? 'collapsed' : ''}`}>
      <div
        className="notes-header"
        onClick={() => setCollapsed(!collapsed)}
      >
        <span className="notes-toggle">{collapsed ? '▸' : '▾'}</span>
        <span>Speaker Notes</span>
      </div>
      {!collapsed && (
        <textarea
          className="notes-textarea"
          value={slide.notes}
          onChange={(e) => updateSlide(currentSlideIndex, { notes: e.target.value })}
          placeholder="Add speaker notes for this slide..."
        />
      )}
    </div>
  );
}
PresentMode function · typescript · L9-L96 (88 LOC)
src/components/PresentMode.tsx
export function PresentMode() {
  const { presentation, setPresenting, selectSlide, projectPath } =
    usePresentationStore();
  const [currentIndex, setCurrentIndex] = useState(
    usePresentationStore.getState().currentSlideIndex
  );
  const [showSpeaker, setShowSpeaker] = useState(false);
  const viewportRef = useRef<HTMLDivElement>(null);
  const [scale, setScale] = useState(1);

  const totalSlides = presentation.slides.length;
  const slideW = presentation.config.width;
  const slideH = presentation.config.height;

  useEffect(() => {
    const el = viewportRef.current;
    if (!el) return;
    const observer = new ResizeObserver((entries) => {
      for (const entry of entries) {
        const { width, height } = entry.contentRect;
        setScale(Math.min(width / slideW, height / slideH));
      }
    });
    observer.observe(el);
    return () => observer.disconnect();
  }, [slideW, slideH]);

  const goTo = useCallback(
    (index: number) => {
      if (index < 0 || inde
PresentElement function · typescript · L98-L142 (45 LOC)
src/components/PresentMode.tsx
function PresentElement({ element: el, zIndex, projectPath }: { element: SlideElement; zIndex: number; projectPath: string | null }) {
  const pos = el.position;

  switch (el.type) {
    case 'text':
      return <PresentTextElement element={el} zIndex={zIndex} />;

    case 'image': {
      let src = el.src;
      if (!src.startsWith('data:') && projectPath) {
        try { src = convertFileSrc(`${projectPath}/${el.src}`); } catch { /* keep original */ }
      }
      return (
        <img src={src} alt="" style={{
          position: 'absolute', left: pos.x, top: pos.y, width: pos.width, height: pos.height,
          objectFit: 'contain', zIndex,
        }} />
      );
    }

    case 'demo': {
      let src: string | undefined;
      if (projectPath) { try { src = convertFileSrc(`${projectPath}/${el.src}`); } catch { /* skip */ } }
      if (!src) return null;
      return (
        <iframe src={src} sandbox="allow-scripts allow-same-origin" title="demo" style={{
          position
PresentTextElement function · typescript · L144-L173 (30 LOC)
src/components/PresentMode.tsx
function PresentTextElement({ element: el, zIndex }: { element: TextElement; zIndex: number }) {
  const ref = useRef<HTMLDivElement>(null);
  const pos = el.position;
  const preset = TEXT_PRESET_STYLES[el.preset];

  useEffect(() => {
    if (ref.current) {
      // Set raw HTML first, then typeset math if present
      resetMathElement(ref.current, el.html);
      if (containsMath(el.html)) {
        typesetElement(ref.current);
      }
    }
  }, [el.html]);

  return (
    <div ref={ref} style={{
      position: 'absolute', left: pos.x, top: pos.y, width: pos.width, height: pos.height,
      fontFamily: el.fontFamily || preset.fontFamily,
      fontSize: el.fontSize || preset.fontSize,
      fontWeight: preset.fontWeight,
      fontStyle: preset.fontStyle,
      color: el.color || preset.color,
      lineHeight: 1.3,
      padding: '8px 12px',
      overflow: 'hidden',
      zIndex,
    }} />
  );
}
PropertiesPanel function · typescript · L16-L231 (216 LOC)
src/components/PropertiesPanel.tsx
export function PropertiesPanel() {
  const {
    presentation, currentSlideIndex, selectedObject,
    updateSlide, updateElement, updateConfig, moveElementZ, deleteElements,
  } = usePresentationStore();

  const slide = presentation.slides[currentSlideIndex];
  if (!slide) return null;

  const selectedEl = selectedObject?.type === 'element'
    ? slide.elements.find((el) => el.id === selectedObject.id)
    : null;

  const multiEls = selectedObject?.type === 'multi'
    ? slide.elements.filter((el) => selectedObject.ids.includes(el.id))
    : [];

  // Alignment helpers for multi-select (non-arrow elements only)
  const alignableEls = multiEls.filter((el) => el.type !== 'arrow');
  const align = (mode: string) => {
    if (alignableEls.length < 2) return;
    const positions = alignableEls.map((el) => el.position);
    switch (mode) {
      case 'left': {
        const minX = Math.min(...positions.map((p) => p.x));
        alignableEls.forEach((el) => updateElement(el.id, { position
PropSection function · typescript · L233-L240 (8 LOC)
src/components/PropertiesPanel.tsx
function PropSection({ label, children }: { label: string; children: React.ReactNode }) {
  return (
    <div className="prop-section">
      <div className="prop-label">{label}</div>
      {children}
    </div>
  );
}
SlideEditor function · typescript · L16-L211 (196 LOC)
src/components/SlideEditor.tsx
export function SlideEditor() {
  const {
    presentation, currentSlideIndex, updateSlide,
    addElement, updateElement, deleteElement,
    selectObject, toggleSelectElement, selectedObject, projectPath,
  } = usePresentationStore();

  const slide = presentation.slides[currentSlideIndex];
  const containerRef = useRef<HTMLDivElement>(null);
  const canvasRef = useRef<HTMLDivElement>(null);
  const [scale, setScale] = useState(1);
  const [marquee, setMarquee] = useState<{ x1: number; y1: number; x2: number; y2: number } | null>(null);

  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;
    const observer = new ResizeObserver((entries) => {
      for (const entry of entries) {
        const { width, height } = entry.contentRect;
        const padding = 32;
        setScale(Math.min((width - padding) / SLIDE_WIDTH, (height - padding) / SLIDE_HEIGHT, 1));
      }
    });
    observer.observe(container);
    return () => observer.disconnect();
  
Repobility analyzer · published findings · https://repobility.com
saveImageFromBlob function · typescript · L213-L226 (14 LOC)
src/components/SlideEditor.tsx
async function saveImageFromBlob(blob: File, projectPath: string): Promise<string | null> {
  try {
    const { writeFile, mkdir, exists } = await import('@tauri-apps/plugin-fs');
    const imagesDir = `${projectPath}/images`;
    if (!(await exists(imagesDir))) await mkdir(imagesDir);
    const ext = blob.type.split('/')[1] || 'png';
    const fileName = `pasted-${Date.now()}.${ext}`;
    await writeFile(`${imagesDir}/${fileName}`, new Uint8Array(await blob.arrayBuffer()));
    return `images/${fileName}`;
  } catch (e) {
    console.error('Failed to save pasted image:', e);
    return null;
  }
}
SlideElementRenderer function · typescript · L21-L77 (57 LOC)
src/components/SlideElementRenderer.tsx
export function SlideElementRenderer({
  element, zIndex, scale, projectPath, isSelected, onUpdate, onDelete, onSelect,
}: Props) {
  switch (element.type) {
    case 'text':
      return (
        <DraggableBox
          elementId={element.id}
          position={element.position} zIndex={zIndex} scale={scale}
          className={`el-text el-preset-${element.preset}`}
          isSelected={isSelected}
          onSelect={onSelect} onDelete={onDelete}
          onPositionChange={(pos) => onUpdate({ position: pos } as any)}
        >
          <TextContent element={element} onCommit={(html) => onUpdate({ html } as any)} />
        </DraggableBox>
      );

    case 'image': {
      let src: string;
      if (element.src.startsWith('data:')) src = element.src;
      else if (projectPath) {
        try { src = convertFileSrc(`${projectPath}/${element.src}`); }
        catch { src = element.src; }
      } else src = element.src;
      return (
        <DraggableBox
          elementId={el
SlideSidebar function · typescript · L11-L147 (137 LOC)
src/components/SlideSidebar.tsx
export function SlideSidebar() {
  const {
    presentation,
    currentSlideIndex,
    selectSlide,
    addSlide,
    deleteSlide,
    duplicateSlide,
    moveSlide,
  } = usePresentationStore();

  const [dragging, setDragging] = useState<number | null>(null);
  const [dropTarget, setDropTarget] = useState<number | null>(null);
  const containerRef = useRef<HTMLDivElement>(null);
  const startY = useRef(0);

  const handleContainerPointerMove = useCallback(
    (e: React.PointerEvent) => {
      if (dragging === null) return;
      const thumbs = containerRef.current?.querySelectorAll('.slide-thumbnail');
      if (!thumbs) return;
      let found = false;
      for (let i = 0; i < thumbs.length; i++) {
        const rect = thumbs[i].getBoundingClientRect();
        if (e.clientY >= rect.top && e.clientY <= rect.bottom && i !== dragging) {
          setDropTarget(i); found = true; break;
        }
      }
      if (!found) setDropTarget(null);
    },
    [dragging]
  );

  const hand
SpeakerPanel function · typescript · L9-L86 (78 LOC)
src/components/SpeakerView.tsx
export function SpeakerPanel() {
  const { presentation, currentSlideIndex } = usePresentationStore();
  const [elapsed, setElapsed] = useState(0);
  const [running, setRunning] = useState(false);
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);

  const slide = presentation.slides[currentSlideIndex];
  const nextSlide =
    currentSlideIndex < presentation.slides.length - 1
      ? presentation.slides[currentSlideIndex + 1]
      : null;

  useEffect(() => {
    if (running) {
      intervalRef.current = setInterval(() => {
        setElapsed((e) => e + 1);
      }, 1000);
    }
    return () => {
      if (intervalRef.current) clearInterval(intervalRef.current);
    };
  }, [running]);

  const formatTime = (secs: number) => {
    const m = Math.floor(secs / 60);
    const s = secs % 60;
    return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
  };

  return (
    <div className="speaker-panel">
      <div className="speaker-header">
 
TextFormatToolbar function · typescript · L24-L100 (77 LOC)
src/components/TextFormatToolbar.tsx
export function TextFormatToolbar(_props: Props) {
  const [colorOpen, setColorOpen] = useState(false);
  const [lastColor, setLastColor] = useState('#2563eb');

  // Prevent toolbar clicks from stealing focus from the contentEditable
  const keepFocus = (e: React.MouseEvent) => {
    e.preventDefault();
    e.stopPropagation();
  };

  const exec = (cmd: string, value?: string) => {
    document.execCommand(cmd, false, value);
  };

  return (
    <div className="text-format-toolbar" onMouseDown={keepFocus}>
      <button onClick={() => exec('bold')} title="Bold (Cmd+B)">
        <b>B</b>
      </button>
      <button onClick={() => exec('italic')} title="Italic (Cmd+I)">
        <i>I</i>
      </button>
      <button onClick={() => exec('underline')} title="Underline (Cmd+U)">
        <u>U</u>
      </button>
      <button onClick={() => exec('strikeThrough')} title="Strikethrough">
        <s>S</s>
      </button>
      <span className="tf-divider" />

      {/* Text color */}
     
Toolbar function · typescript · L8-L122 (115 LOC)
src/components/Toolbar.tsx
export function Toolbar() {
  const { presentation, isDirty, setPresenting, setTitle, updateConfig, projectPath } =
    usePresentationStore();
  const [editingTitle, setEditingTitle] = useState(false);
  const [titleDraft, setTitleDraft] = useState('');
  const titleInputRef = useRef<HTMLInputElement>(null);

  // Update window title
  useEffect(() => {
    const dirty = isDirty ? ' *' : '';
    document.title = `${presentation.title}${dirty} — Eigendeck`;
  }, [presentation.title, isDirty]);

  const startEditingTitle = () => {
    setTitleDraft(presentation.title);
    setEditingTitle(true);
    setTimeout(() => titleInputRef.current?.select(), 0);
  };

  const finishEditingTitle = () => {
    const trimmed = titleDraft.trim();
    if (trimmed && trimmed !== presentation.title) {
      setTitle(trimmed);
    }
    setEditingTitle(false);
  };

  const handleSave = async () => {
    try {
      await saveProject();
    } catch (e) {
      console.error('Save failed:', e);
    }
  };
loadMathJax function · typescript · L11-L66 (56 LOC)
src/lib/mathjax.ts
export function loadMathJax(): Promise<any> {
  if (mathjaxReady) return Promise.resolve((window as any).MathJax);
  if (mathjaxPromise) return mathjaxPromise;

  mathjaxPromise = new Promise((resolve, reject) => {
    // Suppress blob: URL errors (BrowserAdaptor Worker)
    window.addEventListener('error', (e) => {
      if (e.filename?.startsWith('blob:')) e.preventDefault();
    });

    // Stub blob Workers (BrowserAdaptor creates one)
    const OrigWorker = window.Worker;
    (window as any).Worker = function FakeWorker(url: string | URL) {
      if (typeof url === 'string' && url.startsWith('blob:')) {
        const fake = {
          postMessage(data: any) {
            setTimeout(() => { if (fake.onmessage) fake.onmessage({ data: { id: data?.id, result: '' } } as any); }, 0);
          },
          terminate() {}, onmessage: null as any, onerror: null as any,
          addEventListener() {}, removeEventListener() {}, dispatchEvent() { return false; },
        };
        return 
getDisplayMathHeight function · typescript · L75-L77 (3 LOC)
src/lib/mathjax.ts
export function getDisplayMathHeight(tex: string): string | undefined {
  return displayMathHeights.get(tex);
}
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
renderMathInHtml function · typescript · L79-L158 (80 LOC)
src/lib/mathjax.ts
export async function renderMathInHtml(html: string): Promise<string> {
  if (!containsMath(html)) return html;

  const MJ = await loadMathJax();
  const parts: string[] = [];
  let i = 0;

  while (i < html.length) {
    // Skip HTML tags
    if (html[i] === '<') {
      const tagEnd = html.indexOf('>', i);
      if (tagEnd !== -1) {
        parts.push(html.slice(i, tagEnd + 1));
        i = tagEnd + 1;
        continue;
      }
    }

    // Display math $$...$$
    if (html[i] === '$' && html[i + 1] === '$') {
      const end = html.indexOf('$$', i + 2);
      if (end !== -1) {
        const tex = html.slice(i + 2, end);
        try {
          MJ.texReset();
          const container = await Promise.race([
            MJ.tex2svgPromise(`{${tex}}`, { display: true }),
            new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 2000)),
          ]);
          const svg = (container as HTMLElement).querySelector('svg');
          if (svg) {
            // Ca
typesetElement function · typescript · L160-L175 (16 LOC)
src/lib/mathjax.ts
export async function typesetElement(element: HTMLElement): Promise<void> {
  const rawHtml = element.getAttribute('data-raw') || element.innerHTML;
  try {
    // Immediately hide $$...$$ blocks to prevent wrapping flash
    element.setAttribute('data-raw', rawHtml);
    element.innerHTML = rawHtml
      .replace(/\$\$([\s\S]+?)\$\$/g, '<div style="text-align:center;color:#999;font-style:italic;white-space:nowrap;overflow:hidden;">⋯</div>')
      .replace(/\$([^\$\n]+?)\$/g, '<span style="color:#999;font-style:italic;">⋯</span>');

    const rendered = await renderMathInHtml(rawHtml);
    element.innerHTML = rendered;
  } catch (e) {
    console.error('typesetElement error:', e);
    element.innerHTML = rawHtml;
  }
}
resetMathElement function · typescript · L177-L180 (4 LOC)
src/lib/mathjax.ts
export function resetMathElement(element: HTMLElement, rawHtml: string): void {
  element.removeAttribute('data-raw');
  element.innerHTML = rawHtml;
}
containsMath function · typescript · L182-L184 (3 LOC)
src/lib/mathjax.ts
export function containsMath(text: string): boolean {
  return /\$\$[\s\S]+?\$\$|\$[^\$\n]+?\$/.test(text);
}
initAutoSave function · typescript · L19-L40 (22 LOC)
src/store/autoSave.ts
export function initAutoSave() {
  usePresentationStore.subscribe((state, prevState) => {
    // Only trigger on presentation data changes
    if (state.presentation === prevState.presentation) return;
    if (!state.projectPath) return;
    if (state.isPresenting) return;

    // Debounce
    if (autoSaveTimer) clearTimeout(autoSaveTimer);
    autoSaveTimer = setTimeout(() => {
      performAutoSave();
    }, AUTO_SAVE_DELAY);
  });

  // Also save on window blur
  window.addEventListener('blur', () => {
    const state = usePresentationStore.getState();
    if (state.isDirty && state.projectPath) {
      performAutoSave();
    }
  });
}
performAutoSave function · typescript · L42-L65 (24 LOC)
src/store/autoSave.ts
async function performAutoSave() {
  const state = usePresentationStore.getState();
  if (!state.projectPath || !state.isDirty) return;

  const json = JSON.stringify(state.presentation, null, 2);

  // Skip if nothing actually changed
  if (json === lastSavedJson) return;

  const jsonPath = `${state.projectPath}/presentation.json`;

  try {
    // Save main file
    await writeTextFile(jsonPath, json);
    lastSavedJson = json;
    state.markClean();
    console.log('Auto-saved');

    // Create timestamped backup
    await createBackup(state.projectPath, json);
  } catch (e) {
    console.error('Auto-save failed:', e);
  }
}
createBackup function · typescript · L67-L78 (12 LOC)
src/store/autoSave.ts
async function createBackup(projectPath: string, json: string) {
  try {
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    const backupPath = `${projectPath}/${BACKUP_PREFIX}${timestamp}.json`;
    await writeTextFile(backupPath, json);

    // Prune old backups
    await pruneBackups(projectPath);
  } catch (e) {
    console.error('Backup failed:', e);
  }
}
pruneBackups function · typescript · L80-L97 (18 LOC)
src/store/autoSave.ts
async function pruneBackups(projectPath: string) {
  try {
    const entries = await readDir(projectPath);
    const backups = entries
      .filter((e) => e.name?.startsWith(BACKUP_PREFIX))
      .sort((a, b) => (a.name || '').localeCompare(b.name || ''));

    // Keep only the last MAX_BACKUPS
    while (backups.length > MAX_BACKUPS) {
      const oldest = backups.shift();
      if (oldest?.name) {
        await remove(`${projectPath}/${oldest.name}`);
      }
    }
  } catch {
    // readDir may fail if permissions are wrong, ignore
  }
}
Want this analysis on your repo? https://repobility.com/scan/
forceSave function · typescript · L102-L108 (7 LOC)
src/store/autoSave.ts
export async function forceSave() {
  if (autoSaveTimer) {
    clearTimeout(autoSaveTimer);
    autoSaveTimer = null;
  }
  await performAutoSave();
}
showError function · typescript · L15-L17 (3 LOC)
src/store/fileOps.ts
async function showError(msg: string) {
  await message(msg, { title: 'Error', kind: 'error' });
}
openProject function · typescript · L19-L44 (26 LOC)
src/store/fileOps.ts
export async function openProject(): Promise<void> {
  const selected = await open({
    directory: true,
    title: 'Open Presentation Project',
  });
  if (!selected) return;

  const projectPath = selected as string;
  const jsonPath = `${projectPath}/presentation.json`;

  try {
    if (!(await exists(jsonPath))) {
      await showError('No presentation.json found in selected directory.');
      return;
    }

    const content = await readTextFile(jsonPath);
    const presentation: Presentation = JSON.parse(content);

    const store = usePresentationStore.getState();
    store.setProjectPath(projectPath);
    store.setPresentation(presentation);
  } catch (e) {
    await showError(`Failed to open project: ${e}`);
  }
}
createProject function · typescript · L46-L73 (28 LOC)
src/store/fileOps.ts
export async function createProject(): Promise<void> {
  const selected = await open({
    directory: true,
    title: 'Select Directory for New Project',
  });
  if (!selected) return;

  const projectPath = selected as string;
  const presentation = createDefaultPresentation();

  try {
    const demosDir = `${projectPath}/demos`;
    const imagesDir = `${projectPath}/images`;
    if (!(await exists(demosDir))) await mkdir(demosDir);
    if (!(await exists(imagesDir))) await mkdir(imagesDir);

    await writeTextFile(
      `${projectPath}/presentation.json`,
      JSON.stringify(presentation, null, 2)
    );

    const store = usePresentationStore.getState();
    store.setProjectPath(projectPath);
    store.setPresentation(presentation);
  } catch (e) {
    await showError(`Failed to create project: ${e}`);
  }
}
saveProject function · typescript · L75-L114 (40 LOC)
src/store/fileOps.ts
export async function saveProject(): Promise<void> {
  const store = usePresentationStore.getState();

  if (!store.projectPath) {
    // No project open — ask user to pick a directory
    const selected = await open({
      directory: true,
      title: 'Choose a folder to save this presentation',
    });
    if (!selected) return;

    const projectPath = selected as string;
    store.setProjectPath(projectPath);

    // Create subdirectories if they don't exist
    try {
      const demosDir = `${projectPath}/demos`;
      const imagesDir = `${projectPath}/images`;
      if (!(await exists(demosDir))) await mkdir(demosDir);
      if (!(await exists(imagesDir))) await mkdir(imagesDir);
    } catch {
      // dirs may already exist
    }
  }

  const projectPath = usePresentationStore.getState().projectPath;
  if (!projectPath) return;

  try {
    const jsonPath = `${projectPath}/presentation.json`;
    const content = JSON.stringify(usePresentationStore.getState().presentation, null
exportPresentation function · typescript · L116-L254 (139 LOC)
src/store/fileOps.ts
export async function exportPresentation(): Promise<void> {
  const store = usePresentationStore.getState();
  const { presentation, projectPath } = store;

  const selected = await save({
    title: 'Export Presentation',
    defaultPath: `${presentation.title.replace(/[^a-zA-Z0-9]/g, '-')}.html`,
    filters: [{ name: 'HTML', extensions: ['html'] }],
  });
  if (!selected) return;

  try {
    const W = presentation.config.width;
    const H = presentation.config.height;
    const meta = [presentation.config.author, presentation.config.venue]
      .filter(Boolean)
      .join(' \u00B7 ');

    const slides: string[] = [];

    for (let i = 0; i < presentation.slides.length; i++) {
      const slide = presentation.slides[i];
      let inner = '';

      // Elements in z-order
      for (const el of slide.elements) {
        const p = el.position;
        switch (el.type) {
          case 'text': {
            const ps = TEXT_PRESET_STYLES[el.preset];
            inner += `<div style=
show function · typescript · L225-L233 (9 LOC)
src/store/fileOps.ts
function show(i) {
  slides.forEach((s, idx) => s.classList.toggle('active', idx === i));
  // Scale to fit
  const vw = window.innerWidth, vh = window.innerHeight;
  const s = slides[i];
  const scale = Math.min(vw / ${W}, vh / ${H});
  s.style.transform = 'scale(' + scale + ')';
  current = i;
}
updateCurrentSlide function · typescript · L60-L70 (11 LOC)
src/store/presentation.ts
function updateCurrentSlide(
  state: PresentationState,
  updater: (slide: Slide) => Slide
): Partial<PresentationState> {
  const slides = [...state.presentation.slides];
  slides[state.currentSlideIndex] = updater(slides[state.currentSlideIndex]);
  return {
    presentation: { ...state.presentation, slides },
    isDirty: true,
  };
}
Source: Repobility analyzer · https://repobility.com
pauseUndo function · typescript · L401-L403 (3 LOC)
src/store/presentation.ts
export function pauseUndo() {
  usePresentationStore.temporal.getState().pause();
}
resumeUndo function · typescript · L406-L408 (3 LOC)
src/store/presentation.ts
export function resumeUndo() {
  usePresentationStore.temporal.getState().resume();
}
main function · rust · L1-L3 (3 LOC)
src-tauri/build.rs
fn main() {
    tauri_build::build()
}
run function · rust · L5-L128 (124 LOC)
src-tauri/src/lib.rs
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_opener::init())
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_fs::init())
        .setup(|app| {
            // macOS app menu
            let app_menu = SubmenuBuilder::new(app, "Eigendeck")
                .about(Some(AboutMetadata {
                    name: Some("Eigendeck".into()),
                    version: Some("0.1.0".into()),
                    ..Default::default()
                }))
                .separator()
                .services()
                .separator()
                .hide()
                .hide_others()
                .show_all()
                .separator()
                .quit()
                .build()?;

            let new_item = MenuItemBuilder::new("New Project")
                .id("new-project")
                .accelerator("CmdOrCtrl+N")
                .build(app)?;
            let open_item = MenuItemBuilder::new("Open Project")
           
main function · rust · L3-L6 (4 LOC)
src-tauri/src/main.rs
fn main() {
    eigendeck_lib::run()
}
createTextElement function · typescript · L146-L170 (25 LOC)
src/types/presentation.ts
export function createTextElement(preset: TextPreset, overrides?: Partial<ElementPosition>): TextElement {
  const defaults: Record<TextPreset, ElementPosition> = {
    title:      { x: 80,  y: 40,  width: 1760, height: 120 },
    body:       { x: 80,  y: 180, width: 1760, height: 800 },
    textbox:    { x: 200, y: 300, width: 800,  height: 300 },
    annotation: { x: 200, y: 700, width: 600,  height: 150 },
    footnote:   { x: 80,  y: 980, width: 1000, height: 60  },
  };

  const defaultText: Record<TextPreset, string> = {
    title: 'Title',
    body: '',
    textbox: 'Text',
    annotation: 'Annotation',
    footnote: 'Footnote',
  };

  return {
    id: crypto.randomUUID(),
    type: 'text',
    preset,
    html: defaultText[preset],
    position: { ...defaults[preset], ...overrides },
  };
}
createDefaultPresentation function · typescript · L172-L196 (25 LOC)
src/types/presentation.ts
export function createDefaultPresentation(): Presentation {
  return {
    title: 'Untitled Presentation',
    theme: 'white',
    slides: [
      {
        id: crypto.randomUUID(),
        layout: 'centered',
        elements: [
          createTextElement('title', { x: 160, y: 400, width: 1600, height: 140 }),
        ],
        notes: '',
      },
    ],
    config: {
      transition: 'slide',
      backgroundTransition: 'fade',
      width: 1920,
      height: 1080,
      showSlideNumber: true,
      author: '',
      venue: '',
    },
  };
}
createBlankSlide function · typescript · L198-L208 (11 LOC)
src/types/presentation.ts
export function createBlankSlide(): Slide {
  return {
    id: crypto.randomUUID(),
    layout: 'default',
    elements: [
      createTextElement('title'),
      createTextElement('body'),
    ],
    notes: '',
  };
}
Repobility analyzer · published findings · https://repobility.com
getSlideNumber function · typescript · L215-L226 (12 LOC)
src/types/presentation.ts
export function getSlideNumber(slides: Slide[], index: number): number {
  let num = 0;
  for (let i = 0; i <= index; i++) {
    const slide = slides[i];
    const prev = i > 0 ? slides[i - 1] : null;
    // Increment number if this slide starts a new group or has no group
    if (!slide.groupId || !prev || prev.groupId !== slide.groupId) {
      num++;
    }
  }
  return num;
}
isGroupChild function · typescript · L229-L234 (6 LOC)
src/types/presentation.ts
export function isGroupChild(slides: Slide[], index: number): boolean {
  const slide = slides[index];
  if (!slide.groupId) return false;
  if (index === 0) return false;
  return slides[index - 1].groupId === slide.groupId;
}
getGroupIndices function · typescript · L237-L244 (8 LOC)
src/types/presentation.ts
export function getGroupIndices(slides: Slide[], index: number): number[] {
  const slide = slides[index];
  if (!slide.groupId) return [index];
  return slides.reduce<number[]>((acc, s, i) => {
    if (s.groupId === slide.groupId) acc.push(i);
    return acc;
  }, []);
}