Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 51 additions & 24 deletions src/canvas-extensions/presentation-canvas-extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default class PresentationCanvasExtension extends CanvasExtension {
isPresentationMode: boolean = false
visitedNodeIds: string[] = []
fullscreenModalObserver: MutationObserver | null = null
presentationUsesFullscreen: boolean = false

isEnabled() { return 'presentationFeatureEnabled' as const }

Expand Down Expand Up @@ -259,11 +260,21 @@ export default class PresentationCanvasExtension extends CanvasExtension {
zoom: canvas.tZoom,
}

// Enter fullscreen mode
const shouldEnterFullscreen = this.plugin.settings.getSetting('fullscreenPresentationEnabled') as boolean
this.presentationUsesFullscreen = shouldEnterFullscreen

canvas.wrapperEl.focus()
canvas.wrapperEl.requestFullscreen()
canvas.wrapperEl.classList.add('presentation-mode')

if (shouldEnterFullscreen) {
try {
await canvas.wrapperEl.requestFullscreen()
} catch (_err) {
// If fullscreen fails, fall back to windowed mode for this session
this.presentationUsesFullscreen = false
}
}

// Lock canvas
canvas.setReadonly(true)

Expand All @@ -272,7 +283,14 @@ export default class PresentationCanvasExtension extends CanvasExtension {
canvas.screenshotting = true

// Register event handler for keyboard navigation
canvas.wrapperEl.onkeydown = (e: any) => {
canvas.wrapperEl.onkeydown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
e.preventDefault()
e.stopPropagation()
this.endPresentation(canvas)
return
}

if (this.plugin.settings.getSetting('useArrowKeysToChangeSlides')) {
if (e.key === 'ArrowRight') this.nextNode(canvas)
else if (e.key === 'ArrowLeft') this.previousNode(canvas)
Expand All @@ -284,30 +302,34 @@ export default class PresentationCanvasExtension extends CanvasExtension {
}
}

// Keep modals while in fullscreen mode
this.fullscreenModalObserver = new MutationObserver((mutationRecords) => {
mutationRecords.forEach((mutationRecord) => {
mutationRecord.addedNodes.forEach((node) => {
document.body.removeChild(node)
document.fullscreenElement?.appendChild(node)
if (this.presentationUsesFullscreen) {
// Keep modals while in fullscreen mode
this.fullscreenModalObserver = new MutationObserver((mutationRecords) => {
mutationRecords.forEach((mutationRecord) => {
mutationRecord.addedNodes.forEach((node) => {
document.body.removeChild(node)
document.fullscreenElement?.appendChild(node)
})
})
})

const inputField = document.querySelector(".prompt-input") as HTMLInputElement|null
if (inputField) inputField.focus()
})
this.fullscreenModalObserver.observe(document.body, { childList: true })
const inputField = document.querySelector('.prompt-input') as HTMLInputElement | null
if (inputField) inputField.focus()
})
this.fullscreenModalObserver.observe(document.body, { childList: true })

// Register event handler for exiting presentation mode
canvas.wrapperEl.onfullscreenchange = (_e: any) => {
if (document.fullscreenElement) return
this.endPresentation(canvas)
// Register event handler for exiting presentation mode
canvas.wrapperEl.onfullscreenchange = (_e: any) => {
if (document.fullscreenElement) return
this.endPresentation(canvas)
}
} else {
canvas.wrapperEl.onfullscreenchange = null
}

this.isPresentationMode = true

// Wait for fullscreen to be enabled
await sleep(500)
if (this.presentationUsesFullscreen) await sleep(500)

// Zoom to first node
const startNodeId = this.visitedNodeIds.first()
Expand All @@ -320,11 +342,15 @@ export default class PresentationCanvasExtension extends CanvasExtension {
}

private endPresentation(canvas: Canvas) {
if (!this.isPresentationMode) return

// Unregister event handlers
this.fullscreenModalObserver?.disconnect()
this.fullscreenModalObserver = null
if (this.presentationUsesFullscreen) {
this.fullscreenModalObserver?.disconnect()
this.fullscreenModalObserver = null
}
canvas.wrapperEl.onkeydown = null
canvas.wrapperEl.onfullscreenchange = null
if (this.presentationUsesFullscreen) canvas.wrapperEl.onfullscreenchange = null

// Unlock canvas
canvas.setReadonly(false)
Expand All @@ -335,13 +361,14 @@ export default class PresentationCanvasExtension extends CanvasExtension {

// Exit fullscreen mode
canvas.wrapperEl.classList.remove('presentation-mode')
if (document.fullscreenElement) document.exitFullscreen()
if (this.presentationUsesFullscreen && document.fullscreenElement) document.exitFullscreen()

// Reset viewport
if (this.plugin.settings.getSetting('resetViewportOnPresentationEnd'))
canvas.setViewport(this.savedViewport.x, this.savedViewport.y, this.savedViewport.zoom)

this.isPresentationMode = false
this.presentationUsesFullscreen = false
}

private nextNode(canvas: Canvas) {
Expand Down Expand Up @@ -401,4 +428,4 @@ export default class PresentationCanvasExtension extends CanvasExtension {

this.animateNodeTransition(canvas, fromNode, toNode)
}
}
}
19 changes: 13 additions & 6 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export interface AdvancedCanvasPluginSettingsValues {
usePgUpPgDownKeysToChangeSlides: boolean
zoomToSlideWithoutPadding: boolean
useUnclampedZoomWhilePresenting: boolean
fullscreenPresentationEnabled: boolean
slideTransitionAnimationDuration: number
slideTransitionAnimationIntensity: number

Expand Down Expand Up @@ -184,6 +185,7 @@ export const DEFAULT_SETTINGS_VALUES: AdvancedCanvasPluginSettingsValues = {
usePgUpPgDownKeysToChangeSlides: true,
zoomToSlideWithoutPadding: true,
useUnclampedZoomWhilePresenting: false,
fullscreenPresentationEnabled: true,
slideTransitionAnimationDuration: 0.5,
slideTransitionAnimationIntensity: 1.25,

Expand Down Expand Up @@ -508,6 +510,11 @@ export const SETTINGS = {
description: 'When enabled, the zoom will not be clamped while presenting.',
type: 'boolean'
},
fullscreenPresentationEnabled: {
label: 'Enter fullscreen while presenting',
description: 'When enabled, presentations automatically request fullscreen. Disable to keep Obsidian windowed during presentations.',
type: 'boolean'
},
slideTransitionAnimationDuration: {
label: 'Slide transition animation duration',
description: 'The duration of the slide transition animation in seconds. Set to 0 to disable the animation.',
Expand Down Expand Up @@ -616,9 +623,9 @@ export const SETTINGS = {
children: { }
},
} as const satisfies {
[key in keyof AdvancedCanvasPluginSettingsValues]: SettingsHeading & {
children: {
[key in keyof AdvancedCanvasPluginSettingsValues]?: Setting
[key in keyof AdvancedCanvasPluginSettingsValues]: SettingsHeading & {
children: {
[key in keyof AdvancedCanvasPluginSettingsValues]?: Setting
}
}
}
Expand Down Expand Up @@ -674,9 +681,9 @@ export class AdvancedCanvasPluginSettingTab extends PluginSettingTab {
// Generate settings from SETTINGS object
for (const [headingId, heading] of Object.entries(SETTINGS) as [string, SettingsHeading][]) {
this.createFeatureHeading(
containerEl,
heading.label,
heading.description,
containerEl,
heading.label,
heading.description,
heading.infoSection,
heading.disableToggle ? null : headingId as keyof AdvancedCanvasPluginSettingsValues
)
Expand Down