Function bodies 50 total
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';
}, [sidebaraddEntry 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 || indePresentElement 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={{
positionPresentTextElement 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, { positionPropSection 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={elSlideSidebar 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 handSpeakerPanel 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) {
// CatypesetElement 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, nullexportPresentation 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;
}, []);
}