158 lines
4.7 KiB
JavaScript
158 lines
4.7 KiB
JavaScript
|
|
import { ref, watch } from 'vue';
|
||
|
|
import Coloris from '@melloware/coloris';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Composable for managing preview rendering with double buffering
|
||
|
|
* Handles iframe transitions, scroll persistence, and PagedJS rendering
|
||
|
|
*/
|
||
|
|
export function usePreviewRenderer({
|
||
|
|
previewFrame1,
|
||
|
|
previewFrame2,
|
||
|
|
stylesheetStore,
|
||
|
|
narrativeStore,
|
||
|
|
handleIframeMouseMove,
|
||
|
|
handleIframeClick,
|
||
|
|
}) {
|
||
|
|
let savedScrollPercentage = 0;
|
||
|
|
const currentFrameIndex = ref(1); // 1 or 2, which iframe is currently visible
|
||
|
|
const isTransitioning = ref(false);
|
||
|
|
let keyboardShortcutHandler = null;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Render preview to hidden iframe with crossfade transition
|
||
|
|
*/
|
||
|
|
const renderPreview = async (shouldReloadFromFile = false) => {
|
||
|
|
if (isTransitioning.value) return;
|
||
|
|
isTransitioning.value = true;
|
||
|
|
|
||
|
|
// Determine which iframe is currently visible and which to render to
|
||
|
|
const visibleFrame =
|
||
|
|
currentFrameIndex.value === 1 ? previewFrame1.value : previewFrame2.value;
|
||
|
|
const hiddenFrame =
|
||
|
|
currentFrameIndex.value === 1 ? previewFrame2.value : previewFrame1.value;
|
||
|
|
|
||
|
|
if (!hiddenFrame) {
|
||
|
|
isTransitioning.value = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Save scroll position from visible frame
|
||
|
|
if (
|
||
|
|
visibleFrame &&
|
||
|
|
visibleFrame.contentWindow &&
|
||
|
|
visibleFrame.contentDocument
|
||
|
|
) {
|
||
|
|
const scrollTop = visibleFrame.contentWindow.scrollY || 0;
|
||
|
|
const scrollHeight =
|
||
|
|
visibleFrame.contentDocument.documentElement.scrollHeight;
|
||
|
|
const clientHeight = visibleFrame.contentWindow.innerHeight;
|
||
|
|
const maxScroll = scrollHeight - clientHeight;
|
||
|
|
|
||
|
|
savedScrollPercentage = maxScroll > 0 ? scrollTop / maxScroll : 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (shouldReloadFromFile || !stylesheetStore.content) {
|
||
|
|
await stylesheetStore.loadStylesheet();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Render to the hidden frame
|
||
|
|
hiddenFrame.srcdoc = `
|
||
|
|
<!DOCTYPE html>
|
||
|
|
<html>
|
||
|
|
<head>
|
||
|
|
<link rel="stylesheet" href="/assets/css/pagedjs-interface.css">
|
||
|
|
<style id="dynamic-styles">${stylesheetStore.content}</style>
|
||
|
|
<script src="https://unpkg.com/pagedjs/dist/paged.polyfill.js"><\/script>
|
||
|
|
</head>
|
||
|
|
<body>${document.getElementById('content-source').innerHTML}</body>
|
||
|
|
</html>
|
||
|
|
`;
|
||
|
|
|
||
|
|
hiddenFrame.onload = () => {
|
||
|
|
// Add event listeners for page and element interactions
|
||
|
|
hiddenFrame.contentDocument.addEventListener(
|
||
|
|
'mousemove',
|
||
|
|
handleIframeMouseMove
|
||
|
|
);
|
||
|
|
hiddenFrame.contentDocument.addEventListener('click', handleIframeClick);
|
||
|
|
|
||
|
|
// Add keyboard shortcut listener to iframe (for when focus is inside iframe)
|
||
|
|
if (keyboardShortcutHandler) {
|
||
|
|
hiddenFrame.contentDocument.addEventListener('keydown', keyboardShortcutHandler);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Close Coloris when clicking in the iframe
|
||
|
|
hiddenFrame.contentDocument.addEventListener('click', () => {
|
||
|
|
Coloris.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
// Wait for PagedJS to finish rendering
|
||
|
|
setTimeout(() => {
|
||
|
|
// Restore scroll position
|
||
|
|
const scrollHeight =
|
||
|
|
hiddenFrame.contentDocument.documentElement.scrollHeight;
|
||
|
|
const clientHeight = hiddenFrame.contentWindow.innerHeight;
|
||
|
|
const maxScroll = scrollHeight - clientHeight;
|
||
|
|
const targetScroll = savedScrollPercentage * maxScroll;
|
||
|
|
|
||
|
|
hiddenFrame.contentWindow.scrollTo(0, targetScroll);
|
||
|
|
|
||
|
|
// Start crossfade transition
|
||
|
|
setTimeout(() => {
|
||
|
|
// Make hidden frame visible (it's already behind)
|
||
|
|
hiddenFrame.style.opacity = '1';
|
||
|
|
hiddenFrame.style.zIndex = '1';
|
||
|
|
|
||
|
|
// Fade out visible frame
|
||
|
|
if (visibleFrame) {
|
||
|
|
visibleFrame.style.opacity = '0';
|
||
|
|
}
|
||
|
|
|
||
|
|
// After fade completes, swap the frames
|
||
|
|
setTimeout(() => {
|
||
|
|
if (visibleFrame) {
|
||
|
|
visibleFrame.style.zIndex = '0';
|
||
|
|
}
|
||
|
|
|
||
|
|
// Swap current frame
|
||
|
|
currentFrameIndex.value = currentFrameIndex.value === 1 ? 2 : 1;
|
||
|
|
isTransitioning.value = false;
|
||
|
|
}, 200); // Match CSS transition duration
|
||
|
|
}, 50); // Small delay to ensure scroll is set
|
||
|
|
}, 200); // Wait for PagedJS
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
// Watch for stylesheet changes and re-render
|
||
|
|
watch(
|
||
|
|
() => stylesheetStore.content,
|
||
|
|
() => {
|
||
|
|
renderPreview();
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
// Re-render when narrative data changes
|
||
|
|
watch(
|
||
|
|
() => narrativeStore.data,
|
||
|
|
() => {
|
||
|
|
if (narrativeStore.data) {
|
||
|
|
renderPreview();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Set the keyboard shortcut handler (called after keyboard shortcuts composable is initialized)
|
||
|
|
*/
|
||
|
|
const setKeyboardShortcutHandler = (handler) => {
|
||
|
|
keyboardShortcutHandler = handler;
|
||
|
|
};
|
||
|
|
|
||
|
|
return {
|
||
|
|
renderPreview,
|
||
|
|
currentFrameIndex,
|
||
|
|
isTransitioning,
|
||
|
|
setKeyboardShortcutHandler,
|
||
|
|
};
|
||
|
|
}
|